Refactor move type-in code
[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 InitBackEnd1()
663 {
664     int matched, min, sec;
665
666     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
667     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
668
669     GetTimeMark(&programStartTime);
670     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
671
672     ClearProgramStats();
673     programStats.ok_to_send = 1;
674     programStats.seen_stat = 0;
675
676     /*
677      * Initialize game list
678      */
679     ListNew(&gameList);
680
681
682     /*
683      * Internet chess server status
684      */
685     if (appData.icsActive) {
686         appData.matchMode = FALSE;
687         appData.matchGames = 0;
688 #if ZIPPY
689         appData.noChessProgram = !appData.zippyPlay;
690 #else
691         appData.zippyPlay = FALSE;
692         appData.zippyTalk = FALSE;
693         appData.noChessProgram = TRUE;
694 #endif
695         if (*appData.icsHelper != NULLCHAR) {
696             appData.useTelnet = TRUE;
697             appData.telnetProgram = appData.icsHelper;
698         }
699     } else {
700         appData.zippyTalk = appData.zippyPlay = FALSE;
701     }
702
703     /* [AS] Initialize pv info list [HGM] and game state */
704     {
705         int i, j;
706
707         for( i=0; i<=framePtr; i++ ) {
708             pvInfoList[i].depth = -1;
709             boards[i][EP_STATUS] = EP_NONE;
710             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
711         }
712     }
713
714     /*
715      * Parse timeControl resource
716      */
717     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
718                           appData.movesPerSession)) {
719         char buf[MSG_SIZ];
720         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
721         DisplayFatalError(buf, 0, 2);
722     }
723
724     /*
725      * Parse searchTime resource
726      */
727     if (*appData.searchTime != NULLCHAR) {
728         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
729         if (matched == 1) {
730             searchTime = min * 60;
731         } else if (matched == 2) {
732             searchTime = min * 60 + sec;
733         } else {
734             char buf[MSG_SIZ];
735             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
736             DisplayFatalError(buf, 0, 2);
737         }
738     }
739
740     /* [AS] Adjudication threshold */
741     adjudicateLossThreshold = appData.adjudicateLossThreshold;
742
743     first.which = "first";
744     second.which = "second";
745     first.maybeThinking = second.maybeThinking = FALSE;
746     first.pr = second.pr = NoProc;
747     first.isr = second.isr = NULL;
748     first.sendTime = second.sendTime = 2;
749     first.sendDrawOffers = 1;
750     if (appData.firstPlaysBlack) {
751         first.twoMachinesColor = "black\n";
752         second.twoMachinesColor = "white\n";
753     } else {
754         first.twoMachinesColor = "white\n";
755         second.twoMachinesColor = "black\n";
756     }
757     first.program = appData.firstChessProgram;
758     second.program = appData.secondChessProgram;
759     first.host = appData.firstHost;
760     second.host = appData.secondHost;
761     first.dir = appData.firstDirectory;
762     second.dir = appData.secondDirectory;
763     first.other = &second;
764     second.other = &first;
765     first.initString = appData.initString;
766     second.initString = appData.secondInitString;
767     first.computerString = appData.firstComputerString;
768     second.computerString = appData.secondComputerString;
769     first.useSigint = second.useSigint = TRUE;
770     first.useSigterm = second.useSigterm = TRUE;
771     first.reuse = appData.reuseFirst;
772     second.reuse = appData.reuseSecond;
773     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
774     second.nps = appData.secondNPS;
775     first.useSetboard = second.useSetboard = FALSE;
776     first.useSAN = second.useSAN = FALSE;
777     first.usePing = second.usePing = FALSE;
778     first.lastPing = second.lastPing = 0;
779     first.lastPong = second.lastPong = 0;
780     first.usePlayother = second.usePlayother = FALSE;
781     first.useColors = second.useColors = TRUE;
782     first.useUsermove = second.useUsermove = FALSE;
783     first.sendICS = second.sendICS = FALSE;
784     first.sendName = second.sendName = appData.icsActive;
785     first.sdKludge = second.sdKludge = FALSE;
786     first.stKludge = second.stKludge = FALSE;
787     TidyProgramName(first.program, first.host, first.tidy);
788     TidyProgramName(second.program, second.host, second.tidy);
789     first.matchWins = second.matchWins = 0;
790     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
791     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
792     first.analysisSupport = second.analysisSupport = 2; /* detect */
793     first.analyzing = second.analyzing = FALSE;
794     first.initDone = second.initDone = FALSE;
795
796     /* New features added by Tord: */
797     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
798     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
799     /* End of new features added by Tord. */
800     first.fenOverride  = appData.fenOverride1;
801     second.fenOverride = appData.fenOverride2;
802
803     /* [HGM] time odds: set factor for each machine */
804     first.timeOdds  = appData.firstTimeOdds;
805     second.timeOdds = appData.secondTimeOdds;
806     { float norm = 1;
807         if(appData.timeOddsMode) {
808             norm = first.timeOdds;
809             if(norm > second.timeOdds) norm = second.timeOdds;
810         }
811         first.timeOdds /= norm;
812         second.timeOdds /= norm;
813     }
814
815     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
816     first.accumulateTC = appData.firstAccumulateTC;
817     second.accumulateTC = appData.secondAccumulateTC;
818     first.maxNrOfSessions = second.maxNrOfSessions = 1;
819
820     /* [HGM] debug */
821     first.debug = second.debug = FALSE;
822     first.supportsNPS = second.supportsNPS = UNKNOWN;
823
824     /* [HGM] options */
825     first.optionSettings  = appData.firstOptions;
826     second.optionSettings = appData.secondOptions;
827
828     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
829     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
830     first.isUCI = appData.firstIsUCI; /* [AS] */
831     second.isUCI = appData.secondIsUCI; /* [AS] */
832     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
833     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
834
835     if (appData.firstProtocolVersion > PROTOVER
836         || appData.firstProtocolVersion < 1)
837       {
838         char buf[MSG_SIZ];
839         int len;
840
841         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
842                        appData.firstProtocolVersion);
843         if( (len > MSG_SIZ) && appData.debugMode )
844           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
845
846         DisplayFatalError(buf, 0, 2);
847       }
848     else
849       {
850         first.protocolVersion = appData.firstProtocolVersion;
851       }
852
853     if (appData.secondProtocolVersion > PROTOVER
854         || appData.secondProtocolVersion < 1)
855       {
856         char buf[MSG_SIZ];
857         int len;
858
859         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860                        appData.secondProtocolVersion);
861         if( (len > MSG_SIZ) && appData.debugMode )
862           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863
864         DisplayFatalError(buf, 0, 2);
865       }
866     else
867       {
868         second.protocolVersion = appData.secondProtocolVersion;
869       }
870
871     if (appData.icsActive) {
872         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
873 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
874     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
875         appData.clockMode = FALSE;
876         first.sendTime = second.sendTime = 0;
877     }
878
879 #if ZIPPY
880     /* Override some settings from environment variables, for backward
881        compatibility.  Unfortunately it's not feasible to have the env
882        vars just set defaults, at least in xboard.  Ugh.
883     */
884     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
885       ZippyInit();
886     }
887 #endif
888
889     if (appData.noChessProgram) {
890         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
891         sprintf(programVersion, "%s", PACKAGE_STRING);
892     } else {
893       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
894       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
895       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
896     }
897
898     if (!appData.icsActive) {
899       char buf[MSG_SIZ];
900       int len;
901
902       /* Check for variants that are supported only in ICS mode,
903          or not at all.  Some that are accepted here nevertheless
904          have bugs; see comments below.
905       */
906       VariantClass variant = StringToVariant(appData.variant);
907       switch (variant) {
908       case VariantBughouse:     /* need four players and two boards */
909       case VariantKriegspiel:   /* need to hide pieces and move details */
910         /* case VariantFischeRandom: (Fabien: moved below) */
911         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
912         if( (len > MSG_SIZ) && appData.debugMode )
913           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
914
915         DisplayFatalError(buf, 0, 2);
916         return;
917
918       case VariantUnknown:
919       case VariantLoadable:
920       case Variant29:
921       case Variant30:
922       case Variant31:
923       case Variant32:
924       case Variant33:
925       case Variant34:
926       case Variant35:
927       case Variant36:
928       default:
929         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
930         if( (len > MSG_SIZ) && appData.debugMode )
931           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
932
933         DisplayFatalError(buf, 0, 2);
934         return;
935
936       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
937       case VariantFairy:      /* [HGM] TestLegality definitely off! */
938       case VariantGothic:     /* [HGM] should work */
939       case VariantCapablanca: /* [HGM] should work */
940       case VariantCourier:    /* [HGM] initial forced moves not implemented */
941       case VariantShogi:      /* [HGM] could still mate with pawn drop */
942       case VariantKnightmate: /* [HGM] should work */
943       case VariantCylinder:   /* [HGM] untested */
944       case VariantFalcon:     /* [HGM] untested */
945       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
946                                  offboard interposition not understood */
947       case VariantNormal:     /* definitely works! */
948       case VariantWildCastle: /* pieces not automatically shuffled */
949       case VariantNoCastle:   /* pieces not automatically shuffled */
950       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
951       case VariantLosers:     /* should work except for win condition,
952                                  and doesn't know captures are mandatory */
953       case VariantSuicide:    /* should work except for win condition,
954                                  and doesn't know captures are mandatory */
955       case VariantGiveaway:   /* should work except for win condition,
956                                  and doesn't know captures are mandatory */
957       case VariantTwoKings:   /* should work */
958       case VariantAtomic:     /* should work except for win condition */
959       case Variant3Check:     /* should work except for win condition */
960       case VariantShatranj:   /* should work except for all win conditions */
961       case VariantMakruk:     /* should work except for daw countdown */
962       case VariantBerolina:   /* might work if TestLegality is off */
963       case VariantCapaRandom: /* should work */
964       case VariantJanus:      /* should work */
965       case VariantSuper:      /* experimental */
966       case VariantGreat:      /* experimental, requires legality testing to be off */
967       case VariantSChess:     /* S-Chess, should work */
968       case VariantSpartan:    /* should work */
969         break;
970       }
971     }
972
973     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
974     InitEngineUCI( installDir, &second );
975 }
976
977 int NextIntegerFromString( char ** str, long * value )
978 {
979     int result = -1;
980     char * s = *str;
981
982     while( *s == ' ' || *s == '\t' ) {
983         s++;
984     }
985
986     *value = 0;
987
988     if( *s >= '0' && *s <= '9' ) {
989         while( *s >= '0' && *s <= '9' ) {
990             *value = *value * 10 + (*s - '0');
991             s++;
992         }
993
994         result = 0;
995     }
996
997     *str = s;
998
999     return result;
1000 }
1001
1002 int NextTimeControlFromString( char ** str, long * value )
1003 {
1004     long temp;
1005     int result = NextIntegerFromString( str, &temp );
1006
1007     if( result == 0 ) {
1008         *value = temp * 60; /* Minutes */
1009         if( **str == ':' ) {
1010             (*str)++;
1011             result = NextIntegerFromString( str, &temp );
1012             *value += temp; /* Seconds */
1013         }
1014     }
1015
1016     return result;
1017 }
1018
1019 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1020 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1021     int result = -1, type = 0; long temp, temp2;
1022
1023     if(**str != ':') return -1; // old params remain in force!
1024     (*str)++;
1025     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1026     if( NextIntegerFromString( str, &temp ) ) return -1;
1027     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1028
1029     if(**str != '/') {
1030         /* time only: incremental or sudden-death time control */
1031         if(**str == '+') { /* increment follows; read it */
1032             (*str)++;
1033             if(**str == '!') type = *(*str)++; // Bronstein TC
1034             if(result = NextIntegerFromString( str, &temp2)) return -1;
1035             *inc = temp2 * 1000;
1036             if(**str == '.') { // read fraction of increment
1037                 char *start = ++(*str);
1038                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1039                 temp2 *= 1000;
1040                 while(start++ < *str) temp2 /= 10;
1041                 *inc += temp2;
1042             }
1043         } else *inc = 0;
1044         *moves = 0; *tc = temp * 1000; *incType = type;
1045         return 0;
1046     }
1047
1048     (*str)++; /* classical time control */
1049     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1050
1051     if(result == 0) {
1052         *moves = temp;
1053         *tc    = temp2 * 1000;
1054         *inc   = 0;
1055         *incType = type;
1056     }
1057     return result;
1058 }
1059
1060 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1061 {   /* [HGM] get time to add from the multi-session time-control string */
1062     int incType, moves=1; /* kludge to force reading of first session */
1063     long time, increment;
1064     char *s = tcString;
1065
1066     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1067     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1068     do {
1069         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1070         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1071         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1072         if(movenr == -1) return time;    /* last move before new session     */
1073         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1074         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1075         if(!moves) return increment;     /* current session is incremental   */
1076         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1077     } while(movenr >= -1);               /* try again for next session       */
1078
1079     return 0; // no new time quota on this move
1080 }
1081
1082 int
1083 ParseTimeControl(tc, ti, mps)
1084      char *tc;
1085      float ti;
1086      int mps;
1087 {
1088   long tc1;
1089   long tc2;
1090   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1091   int min, sec=0;
1092
1093   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1094   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1095       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1096   if(ti > 0) {
1097
1098     if(mps)
1099       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1100     else 
1101       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1102   } else {
1103     if(mps)
1104       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1105     else 
1106       snprintf(buf, MSG_SIZ, ":%s", mytc);
1107   }
1108   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1109   
1110   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1111     return FALSE;
1112   }
1113
1114   if( *tc == '/' ) {
1115     /* Parse second time control */
1116     tc++;
1117
1118     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1119       return FALSE;
1120     }
1121
1122     if( tc2 == 0 ) {
1123       return FALSE;
1124     }
1125
1126     timeControl_2 = tc2 * 1000;
1127   }
1128   else {
1129     timeControl_2 = 0;
1130   }
1131
1132   if( tc1 == 0 ) {
1133     return FALSE;
1134   }
1135
1136   timeControl = tc1 * 1000;
1137
1138   if (ti >= 0) {
1139     timeIncrement = ti * 1000;  /* convert to ms */
1140     movesPerSession = 0;
1141   } else {
1142     timeIncrement = 0;
1143     movesPerSession = mps;
1144   }
1145   return TRUE;
1146 }
1147
1148 void
1149 InitBackEnd2()
1150 {
1151     if (appData.debugMode) {
1152         fprintf(debugFP, "%s\n", programVersion);
1153     }
1154
1155     set_cont_sequence(appData.wrapContSeq);
1156     if (appData.matchGames > 0) {
1157         appData.matchMode = TRUE;
1158     } else if (appData.matchMode) {
1159         appData.matchGames = 1;
1160     }
1161     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1162         appData.matchGames = appData.sameColorGames;
1163     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1164         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1165         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1166     }
1167     Reset(TRUE, FALSE);
1168     if (appData.noChessProgram || first.protocolVersion == 1) {
1169       InitBackEnd3();
1170     } else {
1171       /* kludge: allow timeout for initial "feature" commands */
1172       FreezeUI();
1173       DisplayMessage("", _("Starting chess program"));
1174       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1175     }
1176 }
1177
1178 void
1179 MatchEvent(int mode)
1180 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1181         /* Set up machine vs. machine match */
1182         if (appData.noChessProgram) {
1183             DisplayFatalError(_("Can't have a match with no chess programs"),
1184                               0, 2);
1185             return;
1186         }
1187         matchMode = mode;
1188         matchGame = 1;
1189         if (*appData.loadGameFile != NULLCHAR) {
1190             int index = appData.loadGameIndex; // [HGM] autoinc
1191             if(index<0) lastIndex = index = 1;
1192             if (!LoadGameFromFile(appData.loadGameFile,
1193                                   index,
1194                                   appData.loadGameFile, FALSE)) {
1195                 DisplayFatalError(_("Bad game file"), 0, 1);
1196                 return;
1197             }
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             int index = appData.loadPositionIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadPositionFromFile(appData.loadPositionFile,
1202                                       index,
1203                                       appData.loadPositionFile)) {
1204                 DisplayFatalError(_("Bad position file"), 0, 1);
1205                 return;
1206             }
1207         }
1208         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1209         TwoMachinesEvent();
1210 }
1211
1212 void
1213 InitBackEnd3 P((void))
1214 {
1215     GameMode initialMode;
1216     char buf[MSG_SIZ];
1217     int err, len;
1218
1219     InitChessProgram(&first, startedFromSetupPosition);
1220
1221     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1222         free(programVersion);
1223         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1224         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1225     }
1226
1227     if (appData.icsActive) {
1228 #ifdef WIN32
1229         /* [DM] Make a console window if needed [HGM] merged ifs */
1230         ConsoleCreate();
1231 #endif
1232         err = establish();
1233         if (err != 0)
1234           {
1235             if (*appData.icsCommPort != NULLCHAR)
1236               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1237                              appData.icsCommPort);
1238             else
1239               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1240                         appData.icsHost, appData.icsPort);
1241
1242             if( (len > MSG_SIZ) && appData.debugMode )
1243               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1244
1245             DisplayFatalError(buf, err, 1);
1246             return;
1247         }
1248         SetICSMode();
1249         telnetISR =
1250           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1251         fromUserISR =
1252           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1253         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1254             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1255     } else if (appData.noChessProgram) {
1256         SetNCPMode();
1257     } else {
1258         SetGNUMode();
1259     }
1260
1261     if (*appData.cmailGameName != NULLCHAR) {
1262         SetCmailMode();
1263         OpenLoopback(&cmailPR);
1264         cmailISR =
1265           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1266     }
1267
1268     ThawUI();
1269     DisplayMessage("", "");
1270     if (StrCaseCmp(appData.initialMode, "") == 0) {
1271       initialMode = BeginningOfGame;
1272     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1273       initialMode = TwoMachinesPlay;
1274     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1275       initialMode = AnalyzeFile;
1276     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1277       initialMode = AnalyzeMode;
1278     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1279       initialMode = MachinePlaysWhite;
1280     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1281       initialMode = MachinePlaysBlack;
1282     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1283       initialMode = EditGame;
1284     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1285       initialMode = EditPosition;
1286     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1287       initialMode = Training;
1288     } else {
1289       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1290       if( (len > MSG_SIZ) && appData.debugMode )
1291         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1292
1293       DisplayFatalError(buf, 0, 2);
1294       return;
1295     }
1296
1297     if (appData.matchMode) {
1298         MatchEvent(TRUE);
1299     } else if (*appData.cmailGameName != NULLCHAR) {
1300         /* Set up cmail mode */
1301         ReloadCmailMsgEvent(TRUE);
1302     } else {
1303         /* Set up other modes */
1304         if (initialMode == AnalyzeFile) {
1305           if (*appData.loadGameFile == NULLCHAR) {
1306             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1307             return;
1308           }
1309         }
1310         if (*appData.loadGameFile != NULLCHAR) {
1311             (void) LoadGameFromFile(appData.loadGameFile,
1312                                     appData.loadGameIndex,
1313                                     appData.loadGameFile, TRUE);
1314         } else if (*appData.loadPositionFile != NULLCHAR) {
1315             (void) LoadPositionFromFile(appData.loadPositionFile,
1316                                         appData.loadPositionIndex,
1317                                         appData.loadPositionFile);
1318             /* [HGM] try to make self-starting even after FEN load */
1319             /* to allow automatic setup of fairy variants with wtm */
1320             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1321                 gameMode = BeginningOfGame;
1322                 setboardSpoiledMachineBlack = 1;
1323             }
1324             /* [HGM] loadPos: make that every new game uses the setup */
1325             /* from file as long as we do not switch variant          */
1326             if(!blackPlaysFirst) {
1327                 startedFromPositionFile = TRUE;
1328                 CopyBoard(filePosition, boards[0]);
1329             }
1330         }
1331         if (initialMode == AnalyzeMode) {
1332           if (appData.noChessProgram) {
1333             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1334             return;
1335           }
1336           if (appData.icsActive) {
1337             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1338             return;
1339           }
1340           AnalyzeModeEvent();
1341         } else if (initialMode == AnalyzeFile) {
1342           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1343           ShowThinkingEvent();
1344           AnalyzeFileEvent();
1345           AnalysisPeriodicEvent(1);
1346         } else if (initialMode == MachinePlaysWhite) {
1347           if (appData.noChessProgram) {
1348             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1349                               0, 2);
1350             return;
1351           }
1352           if (appData.icsActive) {
1353             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1354                               0, 2);
1355             return;
1356           }
1357           MachineWhiteEvent();
1358         } else if (initialMode == MachinePlaysBlack) {
1359           if (appData.noChessProgram) {
1360             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1361                               0, 2);
1362             return;
1363           }
1364           if (appData.icsActive) {
1365             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1366                               0, 2);
1367             return;
1368           }
1369           MachineBlackEvent();
1370         } else if (initialMode == TwoMachinesPlay) {
1371           if (appData.noChessProgram) {
1372             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1373                               0, 2);
1374             return;
1375           }
1376           if (appData.icsActive) {
1377             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1378                               0, 2);
1379             return;
1380           }
1381           TwoMachinesEvent();
1382         } else if (initialMode == EditGame) {
1383           EditGameEvent();
1384         } else if (initialMode == EditPosition) {
1385           EditPositionEvent();
1386         } else if (initialMode == Training) {
1387           if (*appData.loadGameFile == NULLCHAR) {
1388             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1389             return;
1390           }
1391           TrainingEvent();
1392         }
1393     }
1394 }
1395
1396 /*
1397  * Establish will establish a contact to a remote host.port.
1398  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1399  *  used to talk to the host.
1400  * Returns 0 if okay, error code if not.
1401  */
1402 int
1403 establish()
1404 {
1405     char buf[MSG_SIZ];
1406
1407     if (*appData.icsCommPort != NULLCHAR) {
1408         /* Talk to the host through a serial comm port */
1409         return OpenCommPort(appData.icsCommPort, &icsPR);
1410
1411     } else if (*appData.gateway != NULLCHAR) {
1412         if (*appData.remoteShell == NULLCHAR) {
1413             /* Use the rcmd protocol to run telnet program on a gateway host */
1414             snprintf(buf, sizeof(buf), "%s %s %s",
1415                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1416             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1417
1418         } else {
1419             /* Use the rsh program to run telnet program on a gateway host */
1420             if (*appData.remoteUser == NULLCHAR) {
1421                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1422                         appData.gateway, appData.telnetProgram,
1423                         appData.icsHost, appData.icsPort);
1424             } else {
1425                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1426                         appData.remoteShell, appData.gateway,
1427                         appData.remoteUser, appData.telnetProgram,
1428                         appData.icsHost, appData.icsPort);
1429             }
1430             return StartChildProcess(buf, "", &icsPR);
1431
1432         }
1433     } else if (appData.useTelnet) {
1434         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1435
1436     } else {
1437         /* TCP socket interface differs somewhat between
1438            Unix and NT; handle details in the front end.
1439            */
1440         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1441     }
1442 }
1443
1444 void EscapeExpand(char *p, char *q)
1445 {       // [HGM] initstring: routine to shape up string arguments
1446         while(*p++ = *q++) if(p[-1] == '\\')
1447             switch(*q++) {
1448                 case 'n': p[-1] = '\n'; break;
1449                 case 'r': p[-1] = '\r'; break;
1450                 case 't': p[-1] = '\t'; break;
1451                 case '\\': p[-1] = '\\'; break;
1452                 case 0: *p = 0; return;
1453                 default: p[-1] = q[-1]; break;
1454             }
1455 }
1456
1457 void
1458 show_bytes(fp, buf, count)
1459      FILE *fp;
1460      char *buf;
1461      int count;
1462 {
1463     while (count--) {
1464         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1465             fprintf(fp, "\\%03o", *buf & 0xff);
1466         } else {
1467             putc(*buf, fp);
1468         }
1469         buf++;
1470     }
1471     fflush(fp);
1472 }
1473
1474 /* Returns an errno value */
1475 int
1476 OutputMaybeTelnet(pr, message, count, outError)
1477      ProcRef pr;
1478      char *message;
1479      int count;
1480      int *outError;
1481 {
1482     char buf[8192], *p, *q, *buflim;
1483     int left, newcount, outcount;
1484
1485     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1486         *appData.gateway != NULLCHAR) {
1487         if (appData.debugMode) {
1488             fprintf(debugFP, ">ICS: ");
1489             show_bytes(debugFP, message, count);
1490             fprintf(debugFP, "\n");
1491         }
1492         return OutputToProcess(pr, message, count, outError);
1493     }
1494
1495     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1496     p = message;
1497     q = buf;
1498     left = count;
1499     newcount = 0;
1500     while (left) {
1501         if (q >= buflim) {
1502             if (appData.debugMode) {
1503                 fprintf(debugFP, ">ICS: ");
1504                 show_bytes(debugFP, buf, newcount);
1505                 fprintf(debugFP, "\n");
1506             }
1507             outcount = OutputToProcess(pr, buf, newcount, outError);
1508             if (outcount < newcount) return -1; /* to be sure */
1509             q = buf;
1510             newcount = 0;
1511         }
1512         if (*p == '\n') {
1513             *q++ = '\r';
1514             newcount++;
1515         } else if (((unsigned char) *p) == TN_IAC) {
1516             *q++ = (char) TN_IAC;
1517             newcount ++;
1518         }
1519         *q++ = *p++;
1520         newcount++;
1521         left--;
1522     }
1523     if (appData.debugMode) {
1524         fprintf(debugFP, ">ICS: ");
1525         show_bytes(debugFP, buf, newcount);
1526         fprintf(debugFP, "\n");
1527     }
1528     outcount = OutputToProcess(pr, buf, newcount, outError);
1529     if (outcount < newcount) return -1; /* to be sure */
1530     return count;
1531 }
1532
1533 void
1534 read_from_player(isr, closure, message, count, error)
1535      InputSourceRef isr;
1536      VOIDSTAR closure;
1537      char *message;
1538      int count;
1539      int error;
1540 {
1541     int outError, outCount;
1542     static int gotEof = 0;
1543
1544     /* Pass data read from player on to ICS */
1545     if (count > 0) {
1546         gotEof = 0;
1547         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1548         if (outCount < count) {
1549             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1550         }
1551     } else if (count < 0) {
1552         RemoveInputSource(isr);
1553         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1554     } else if (gotEof++ > 0) {
1555         RemoveInputSource(isr);
1556         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1557     }
1558 }
1559
1560 void
1561 KeepAlive()
1562 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1563     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1564     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1565     SendToICS("date\n");
1566     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1567 }
1568
1569 /* added routine for printf style output to ics */
1570 void ics_printf(char *format, ...)
1571 {
1572     char buffer[MSG_SIZ];
1573     va_list args;
1574
1575     va_start(args, format);
1576     vsnprintf(buffer, sizeof(buffer), format, args);
1577     buffer[sizeof(buffer)-1] = '\0';
1578     SendToICS(buffer);
1579     va_end(args);
1580 }
1581
1582 void
1583 SendToICS(s)
1584      char *s;
1585 {
1586     int count, outCount, outError;
1587
1588     if (icsPR == NULL) return;
1589
1590     count = strlen(s);
1591     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1592     if (outCount < count) {
1593         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1594     }
1595 }
1596
1597 /* This is used for sending logon scripts to the ICS. Sending
1598    without a delay causes problems when using timestamp on ICC
1599    (at least on my machine). */
1600 void
1601 SendToICSDelayed(s,msdelay)
1602      char *s;
1603      long msdelay;
1604 {
1605     int count, outCount, outError;
1606
1607     if (icsPR == NULL) return;
1608
1609     count = strlen(s);
1610     if (appData.debugMode) {
1611         fprintf(debugFP, ">ICS: ");
1612         show_bytes(debugFP, s, count);
1613         fprintf(debugFP, "\n");
1614     }
1615     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1616                                       msdelay);
1617     if (outCount < count) {
1618         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1619     }
1620 }
1621
1622
1623 /* Remove all highlighting escape sequences in s
1624    Also deletes any suffix starting with '('
1625    */
1626 char *
1627 StripHighlightAndTitle(s)
1628      char *s;
1629 {
1630     static char retbuf[MSG_SIZ];
1631     char *p = retbuf;
1632
1633     while (*s != NULLCHAR) {
1634         while (*s == '\033') {
1635             while (*s != NULLCHAR && !isalpha(*s)) s++;
1636             if (*s != NULLCHAR) s++;
1637         }
1638         while (*s != NULLCHAR && *s != '\033') {
1639             if (*s == '(' || *s == '[') {
1640                 *p = NULLCHAR;
1641                 return retbuf;
1642             }
1643             *p++ = *s++;
1644         }
1645     }
1646     *p = NULLCHAR;
1647     return retbuf;
1648 }
1649
1650 /* Remove all highlighting escape sequences in s */
1651 char *
1652 StripHighlight(s)
1653      char *s;
1654 {
1655     static char retbuf[MSG_SIZ];
1656     char *p = retbuf;
1657
1658     while (*s != NULLCHAR) {
1659         while (*s == '\033') {
1660             while (*s != NULLCHAR && !isalpha(*s)) s++;
1661             if (*s != NULLCHAR) s++;
1662         }
1663         while (*s != NULLCHAR && *s != '\033') {
1664             *p++ = *s++;
1665         }
1666     }
1667     *p = NULLCHAR;
1668     return retbuf;
1669 }
1670
1671 char *variantNames[] = VARIANT_NAMES;
1672 char *
1673 VariantName(v)
1674      VariantClass v;
1675 {
1676     return variantNames[v];
1677 }
1678
1679
1680 /* Identify a variant from the strings the chess servers use or the
1681    PGN Variant tag names we use. */
1682 VariantClass
1683 StringToVariant(e)
1684      char *e;
1685 {
1686     char *p;
1687     int wnum = -1;
1688     VariantClass v = VariantNormal;
1689     int i, found = FALSE;
1690     char buf[MSG_SIZ];
1691     int len;
1692
1693     if (!e) return v;
1694
1695     /* [HGM] skip over optional board-size prefixes */
1696     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1697         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1698         while( *e++ != '_');
1699     }
1700
1701     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1702         v = VariantNormal;
1703         found = TRUE;
1704     } else
1705     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1706       if (StrCaseStr(e, variantNames[i])) {
1707         v = (VariantClass) i;
1708         found = TRUE;
1709         break;
1710       }
1711     }
1712
1713     if (!found) {
1714       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1715           || StrCaseStr(e, "wild/fr")
1716           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1717         v = VariantFischeRandom;
1718       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1719                  (i = 1, p = StrCaseStr(e, "w"))) {
1720         p += i;
1721         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1722         if (isdigit(*p)) {
1723           wnum = atoi(p);
1724         } else {
1725           wnum = -1;
1726         }
1727         switch (wnum) {
1728         case 0: /* FICS only, actually */
1729         case 1:
1730           /* Castling legal even if K starts on d-file */
1731           v = VariantWildCastle;
1732           break;
1733         case 2:
1734         case 3:
1735         case 4:
1736           /* Castling illegal even if K & R happen to start in
1737              normal positions. */
1738           v = VariantNoCastle;
1739           break;
1740         case 5:
1741         case 7:
1742         case 8:
1743         case 10:
1744         case 11:
1745         case 12:
1746         case 13:
1747         case 14:
1748         case 15:
1749         case 18:
1750         case 19:
1751           /* Castling legal iff K & R start in normal positions */
1752           v = VariantNormal;
1753           break;
1754         case 6:
1755         case 20:
1756         case 21:
1757           /* Special wilds for position setup; unclear what to do here */
1758           v = VariantLoadable;
1759           break;
1760         case 9:
1761           /* Bizarre ICC game */
1762           v = VariantTwoKings;
1763           break;
1764         case 16:
1765           v = VariantKriegspiel;
1766           break;
1767         case 17:
1768           v = VariantLosers;
1769           break;
1770         case 22:
1771           v = VariantFischeRandom;
1772           break;
1773         case 23:
1774           v = VariantCrazyhouse;
1775           break;
1776         case 24:
1777           v = VariantBughouse;
1778           break;
1779         case 25:
1780           v = Variant3Check;
1781           break;
1782         case 26:
1783           /* Not quite the same as FICS suicide! */
1784           v = VariantGiveaway;
1785           break;
1786         case 27:
1787           v = VariantAtomic;
1788           break;
1789         case 28:
1790           v = VariantShatranj;
1791           break;
1792
1793         /* Temporary names for future ICC types.  The name *will* change in
1794            the next xboard/WinBoard release after ICC defines it. */
1795         case 29:
1796           v = Variant29;
1797           break;
1798         case 30:
1799           v = Variant30;
1800           break;
1801         case 31:
1802           v = Variant31;
1803           break;
1804         case 32:
1805           v = Variant32;
1806           break;
1807         case 33:
1808           v = Variant33;
1809           break;
1810         case 34:
1811           v = Variant34;
1812           break;
1813         case 35:
1814           v = Variant35;
1815           break;
1816         case 36:
1817           v = Variant36;
1818           break;
1819         case 37:
1820           v = VariantShogi;
1821           break;
1822         case 38:
1823           v = VariantXiangqi;
1824           break;
1825         case 39:
1826           v = VariantCourier;
1827           break;
1828         case 40:
1829           v = VariantGothic;
1830           break;
1831         case 41:
1832           v = VariantCapablanca;
1833           break;
1834         case 42:
1835           v = VariantKnightmate;
1836           break;
1837         case 43:
1838           v = VariantFairy;
1839           break;
1840         case 44:
1841           v = VariantCylinder;
1842           break;
1843         case 45:
1844           v = VariantFalcon;
1845           break;
1846         case 46:
1847           v = VariantCapaRandom;
1848           break;
1849         case 47:
1850           v = VariantBerolina;
1851           break;
1852         case 48:
1853           v = VariantJanus;
1854           break;
1855         case 49:
1856           v = VariantSuper;
1857           break;
1858         case 50:
1859           v = VariantGreat;
1860           break;
1861         case -1:
1862           /* Found "wild" or "w" in the string but no number;
1863              must assume it's normal chess. */
1864           v = VariantNormal;
1865           break;
1866         default:
1867           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1868           if( (len > MSG_SIZ) && appData.debugMode )
1869             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1870
1871           DisplayError(buf, 0);
1872           v = VariantUnknown;
1873           break;
1874         }
1875       }
1876     }
1877     if (appData.debugMode) {
1878       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1879               e, wnum, VariantName(v));
1880     }
1881     return v;
1882 }
1883
1884 static int leftover_start = 0, leftover_len = 0;
1885 char star_match[STAR_MATCH_N][MSG_SIZ];
1886
1887 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1888    advance *index beyond it, and set leftover_start to the new value of
1889    *index; else return FALSE.  If pattern contains the character '*', it
1890    matches any sequence of characters not containing '\r', '\n', or the
1891    character following the '*' (if any), and the matched sequence(s) are
1892    copied into star_match.
1893    */
1894 int
1895 looking_at(buf, index, pattern)
1896      char *buf;
1897      int *index;
1898      char *pattern;
1899 {
1900     char *bufp = &buf[*index], *patternp = pattern;
1901     int star_count = 0;
1902     char *matchp = star_match[0];
1903
1904     for (;;) {
1905         if (*patternp == NULLCHAR) {
1906             *index = leftover_start = bufp - buf;
1907             *matchp = NULLCHAR;
1908             return TRUE;
1909         }
1910         if (*bufp == NULLCHAR) return FALSE;
1911         if (*patternp == '*') {
1912             if (*bufp == *(patternp + 1)) {
1913                 *matchp = NULLCHAR;
1914                 matchp = star_match[++star_count];
1915                 patternp += 2;
1916                 bufp++;
1917                 continue;
1918             } else if (*bufp == '\n' || *bufp == '\r') {
1919                 patternp++;
1920                 if (*patternp == NULLCHAR)
1921                   continue;
1922                 else
1923                   return FALSE;
1924             } else {
1925                 *matchp++ = *bufp++;
1926                 continue;
1927             }
1928         }
1929         if (*patternp != *bufp) return FALSE;
1930         patternp++;
1931         bufp++;
1932     }
1933 }
1934
1935 void
1936 SendToPlayer(data, length)
1937      char *data;
1938      int length;
1939 {
1940     int error, outCount;
1941     outCount = OutputToProcess(NoProc, data, length, &error);
1942     if (outCount < length) {
1943         DisplayFatalError(_("Error writing to display"), error, 1);
1944     }
1945 }
1946
1947 void
1948 PackHolding(packed, holding)
1949      char packed[];
1950      char *holding;
1951 {
1952     char *p = holding;
1953     char *q = packed;
1954     int runlength = 0;
1955     int curr = 9999;
1956     do {
1957         if (*p == curr) {
1958             runlength++;
1959         } else {
1960             switch (runlength) {
1961               case 0:
1962                 break;
1963               case 1:
1964                 *q++ = curr;
1965                 break;
1966               case 2:
1967                 *q++ = curr;
1968                 *q++ = curr;
1969                 break;
1970               default:
1971                 sprintf(q, "%d", runlength);
1972                 while (*q) q++;
1973                 *q++ = curr;
1974                 break;
1975             }
1976             runlength = 1;
1977             curr = *p;
1978         }
1979     } while (*p++);
1980     *q = NULLCHAR;
1981 }
1982
1983 /* Telnet protocol requests from the front end */
1984 void
1985 TelnetRequest(ddww, option)
1986      unsigned char ddww, option;
1987 {
1988     unsigned char msg[3];
1989     int outCount, outError;
1990
1991     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1992
1993     if (appData.debugMode) {
1994         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1995         switch (ddww) {
1996           case TN_DO:
1997             ddwwStr = "DO";
1998             break;
1999           case TN_DONT:
2000             ddwwStr = "DONT";
2001             break;
2002           case TN_WILL:
2003             ddwwStr = "WILL";
2004             break;
2005           case TN_WONT:
2006             ddwwStr = "WONT";
2007             break;
2008           default:
2009             ddwwStr = buf1;
2010             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2011             break;
2012         }
2013         switch (option) {
2014           case TN_ECHO:
2015             optionStr = "ECHO";
2016             break;
2017           default:
2018             optionStr = buf2;
2019             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2020             break;
2021         }
2022         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2023     }
2024     msg[0] = TN_IAC;
2025     msg[1] = ddww;
2026     msg[2] = option;
2027     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2028     if (outCount < 3) {
2029         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2030     }
2031 }
2032
2033 void
2034 DoEcho()
2035 {
2036     if (!appData.icsActive) return;
2037     TelnetRequest(TN_DO, TN_ECHO);
2038 }
2039
2040 void
2041 DontEcho()
2042 {
2043     if (!appData.icsActive) return;
2044     TelnetRequest(TN_DONT, TN_ECHO);
2045 }
2046
2047 void
2048 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2049 {
2050     /* put the holdings sent to us by the server on the board holdings area */
2051     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2052     char p;
2053     ChessSquare piece;
2054
2055     if(gameInfo.holdingsWidth < 2)  return;
2056     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2057         return; // prevent overwriting by pre-board holdings
2058
2059     if( (int)lowestPiece >= BlackPawn ) {
2060         holdingsColumn = 0;
2061         countsColumn = 1;
2062         holdingsStartRow = BOARD_HEIGHT-1;
2063         direction = -1;
2064     } else {
2065         holdingsColumn = BOARD_WIDTH-1;
2066         countsColumn = BOARD_WIDTH-2;
2067         holdingsStartRow = 0;
2068         direction = 1;
2069     }
2070
2071     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2072         board[i][holdingsColumn] = EmptySquare;
2073         board[i][countsColumn]   = (ChessSquare) 0;
2074     }
2075     while( (p=*holdings++) != NULLCHAR ) {
2076         piece = CharToPiece( ToUpper(p) );
2077         if(piece == EmptySquare) continue;
2078         /*j = (int) piece - (int) WhitePawn;*/
2079         j = PieceToNumber(piece);
2080         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2081         if(j < 0) continue;               /* should not happen */
2082         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2083         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2084         board[holdingsStartRow+j*direction][countsColumn]++;
2085     }
2086 }
2087
2088
2089 void
2090 VariantSwitch(Board board, VariantClass newVariant)
2091 {
2092    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2093    static Board oldBoard;
2094
2095    startedFromPositionFile = FALSE;
2096    if(gameInfo.variant == newVariant) return;
2097
2098    /* [HGM] This routine is called each time an assignment is made to
2099     * gameInfo.variant during a game, to make sure the board sizes
2100     * are set to match the new variant. If that means adding or deleting
2101     * holdings, we shift the playing board accordingly
2102     * This kludge is needed because in ICS observe mode, we get boards
2103     * of an ongoing game without knowing the variant, and learn about the
2104     * latter only later. This can be because of the move list we requested,
2105     * in which case the game history is refilled from the beginning anyway,
2106     * but also when receiving holdings of a crazyhouse game. In the latter
2107     * case we want to add those holdings to the already received position.
2108     */
2109
2110
2111    if (appData.debugMode) {
2112      fprintf(debugFP, "Switch board from %s to %s\n",
2113              VariantName(gameInfo.variant), VariantName(newVariant));
2114      setbuf(debugFP, NULL);
2115    }
2116    shuffleOpenings = 0;       /* [HGM] shuffle */
2117    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2118    switch(newVariant)
2119      {
2120      case VariantShogi:
2121        newWidth = 9;  newHeight = 9;
2122        gameInfo.holdingsSize = 7;
2123      case VariantBughouse:
2124      case VariantCrazyhouse:
2125        newHoldingsWidth = 2; break;
2126      case VariantGreat:
2127        newWidth = 10;
2128      case VariantSuper:
2129        newHoldingsWidth = 2;
2130        gameInfo.holdingsSize = 8;
2131        break;
2132      case VariantGothic:
2133      case VariantCapablanca:
2134      case VariantCapaRandom:
2135        newWidth = 10;
2136      default:
2137        newHoldingsWidth = gameInfo.holdingsSize = 0;
2138      };
2139
2140    if(newWidth  != gameInfo.boardWidth  ||
2141       newHeight != gameInfo.boardHeight ||
2142       newHoldingsWidth != gameInfo.holdingsWidth ) {
2143
2144      /* shift position to new playing area, if needed */
2145      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2146        for(i=0; i<BOARD_HEIGHT; i++)
2147          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2148            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2149              board[i][j];
2150        for(i=0; i<newHeight; i++) {
2151          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2152          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2153        }
2154      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2155        for(i=0; i<BOARD_HEIGHT; i++)
2156          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2157            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2158              board[i][j];
2159      }
2160      gameInfo.boardWidth  = newWidth;
2161      gameInfo.boardHeight = newHeight;
2162      gameInfo.holdingsWidth = newHoldingsWidth;
2163      gameInfo.variant = newVariant;
2164      InitDrawingSizes(-2, 0);
2165    } else gameInfo.variant = newVariant;
2166    CopyBoard(oldBoard, board);   // remember correctly formatted board
2167      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2168    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2169 }
2170
2171 static int loggedOn = FALSE;
2172
2173 /*-- Game start info cache: --*/
2174 int gs_gamenum;
2175 char gs_kind[MSG_SIZ];
2176 static char player1Name[128] = "";
2177 static char player2Name[128] = "";
2178 static char cont_seq[] = "\n\\   ";
2179 static int player1Rating = -1;
2180 static int player2Rating = -1;
2181 /*----------------------------*/
2182
2183 ColorClass curColor = ColorNormal;
2184 int suppressKibitz = 0;
2185
2186 // [HGM] seekgraph
2187 Boolean soughtPending = FALSE;
2188 Boolean seekGraphUp;
2189 #define MAX_SEEK_ADS 200
2190 #define SQUARE 0x80
2191 char *seekAdList[MAX_SEEK_ADS];
2192 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2193 float tcList[MAX_SEEK_ADS];
2194 char colorList[MAX_SEEK_ADS];
2195 int nrOfSeekAds = 0;
2196 int minRating = 1010, maxRating = 2800;
2197 int hMargin = 10, vMargin = 20, h, w;
2198 extern int squareSize, lineGap;
2199
2200 void
2201 PlotSeekAd(int i)
2202 {
2203         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2204         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2205         if(r < minRating+100 && r >=0 ) r = minRating+100;
2206         if(r > maxRating) r = maxRating;
2207         if(tc < 1.) tc = 1.;
2208         if(tc > 95.) tc = 95.;
2209         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2210         y = ((double)r - minRating)/(maxRating - minRating)
2211             * (h-vMargin-squareSize/8-1) + vMargin;
2212         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2213         if(strstr(seekAdList[i], " u ")) color = 1;
2214         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2215            !strstr(seekAdList[i], "bullet") &&
2216            !strstr(seekAdList[i], "blitz") &&
2217            !strstr(seekAdList[i], "standard") ) color = 2;
2218         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2219         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2220 }
2221
2222 void
2223 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2224 {
2225         char buf[MSG_SIZ], *ext = "";
2226         VariantClass v = StringToVariant(type);
2227         if(strstr(type, "wild")) {
2228             ext = type + 4; // append wild number
2229             if(v == VariantFischeRandom) type = "chess960"; else
2230             if(v == VariantLoadable) type = "setup"; else
2231             type = VariantName(v);
2232         }
2233         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2234         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2235             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2236             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2237             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2238             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2239             seekNrList[nrOfSeekAds] = nr;
2240             zList[nrOfSeekAds] = 0;
2241             seekAdList[nrOfSeekAds++] = StrSave(buf);
2242             if(plot) PlotSeekAd(nrOfSeekAds-1);
2243         }
2244 }
2245
2246 void
2247 EraseSeekDot(int i)
2248 {
2249     int x = xList[i], y = yList[i], d=squareSize/4, k;
2250     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2251     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2252     // now replot every dot that overlapped
2253     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2254         int xx = xList[k], yy = yList[k];
2255         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2256             DrawSeekDot(xx, yy, colorList[k]);
2257     }
2258 }
2259
2260 void
2261 RemoveSeekAd(int nr)
2262 {
2263         int i;
2264         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2265             EraseSeekDot(i);
2266             if(seekAdList[i]) free(seekAdList[i]);
2267             seekAdList[i] = seekAdList[--nrOfSeekAds];
2268             seekNrList[i] = seekNrList[nrOfSeekAds];
2269             ratingList[i] = ratingList[nrOfSeekAds];
2270             colorList[i]  = colorList[nrOfSeekAds];
2271             tcList[i] = tcList[nrOfSeekAds];
2272             xList[i]  = xList[nrOfSeekAds];
2273             yList[i]  = yList[nrOfSeekAds];
2274             zList[i]  = zList[nrOfSeekAds];
2275             seekAdList[nrOfSeekAds] = NULL;
2276             break;
2277         }
2278 }
2279
2280 Boolean
2281 MatchSoughtLine(char *line)
2282 {
2283     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2284     int nr, base, inc, u=0; char dummy;
2285
2286     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2287        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2288        (u=1) &&
2289        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2290         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2291         // match: compact and save the line
2292         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2293         return TRUE;
2294     }
2295     return FALSE;
2296 }
2297
2298 int
2299 DrawSeekGraph()
2300 {
2301     int i;
2302     if(!seekGraphUp) return FALSE;
2303     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2304     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2305
2306     DrawSeekBackground(0, 0, w, h);
2307     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2308     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2309     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2310         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2311         yy = h-1-yy;
2312         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2313         if(i%500 == 0) {
2314             char buf[MSG_SIZ];
2315             snprintf(buf, MSG_SIZ, "%d", i);
2316             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2317         }
2318     }
2319     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2320     for(i=1; i<100; i+=(i<10?1:5)) {
2321         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2322         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2323         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2324             char buf[MSG_SIZ];
2325             snprintf(buf, MSG_SIZ, "%d", i);
2326             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2327         }
2328     }
2329     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2330     return TRUE;
2331 }
2332
2333 int SeekGraphClick(ClickType click, int x, int y, int moving)
2334 {
2335     static int lastDown = 0, displayed = 0, lastSecond;
2336     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2337         if(click == Release || moving) return FALSE;
2338         nrOfSeekAds = 0;
2339         soughtPending = TRUE;
2340         SendToICS(ics_prefix);
2341         SendToICS("sought\n"); // should this be "sought all"?
2342     } else { // issue challenge based on clicked ad
2343         int dist = 10000; int i, closest = 0, second = 0;
2344         for(i=0; i<nrOfSeekAds; i++) {
2345             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2346             if(d < dist) { dist = d; closest = i; }
2347             second += (d - zList[i] < 120); // count in-range ads
2348             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2349         }
2350         if(dist < 120) {
2351             char buf[MSG_SIZ];
2352             second = (second > 1);
2353             if(displayed != closest || second != lastSecond) {
2354                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2355                 lastSecond = second; displayed = closest;
2356             }
2357             if(click == Press) {
2358                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2359                 lastDown = closest;
2360                 return TRUE;
2361             } // on press 'hit', only show info
2362             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2363             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2364             SendToICS(ics_prefix);
2365             SendToICS(buf);
2366             return TRUE; // let incoming board of started game pop down the graph
2367         } else if(click == Release) { // release 'miss' is ignored
2368             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2369             if(moving == 2) { // right up-click
2370                 nrOfSeekAds = 0; // refresh graph
2371                 soughtPending = TRUE;
2372                 SendToICS(ics_prefix);
2373                 SendToICS("sought\n"); // should this be "sought all"?
2374             }
2375             return TRUE;
2376         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2377         // press miss or release hit 'pop down' seek graph
2378         seekGraphUp = FALSE;
2379         DrawPosition(TRUE, NULL);
2380     }
2381     return TRUE;
2382 }
2383
2384 void
2385 read_from_ics(isr, closure, data, count, error)
2386      InputSourceRef isr;
2387      VOIDSTAR closure;
2388      char *data;
2389      int count;
2390      int error;
2391 {
2392 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2393 #define STARTED_NONE 0
2394 #define STARTED_MOVES 1
2395 #define STARTED_BOARD 2
2396 #define STARTED_OBSERVE 3
2397 #define STARTED_HOLDINGS 4
2398 #define STARTED_CHATTER 5
2399 #define STARTED_COMMENT 6
2400 #define STARTED_MOVES_NOHIDE 7
2401
2402     static int started = STARTED_NONE;
2403     static char parse[20000];
2404     static int parse_pos = 0;
2405     static char buf[BUF_SIZE + 1];
2406     static int firstTime = TRUE, intfSet = FALSE;
2407     static ColorClass prevColor = ColorNormal;
2408     static int savingComment = FALSE;
2409     static int cmatch = 0; // continuation sequence match
2410     char *bp;
2411     char str[MSG_SIZ];
2412     int i, oldi;
2413     int buf_len;
2414     int next_out;
2415     int tkind;
2416     int backup;    /* [DM] For zippy color lines */
2417     char *p;
2418     char talker[MSG_SIZ]; // [HGM] chat
2419     int channel;
2420
2421     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2422
2423     if (appData.debugMode) {
2424       if (!error) {
2425         fprintf(debugFP, "<ICS: ");
2426         show_bytes(debugFP, data, count);
2427         fprintf(debugFP, "\n");
2428       }
2429     }
2430
2431     if (appData.debugMode) { int f = forwardMostMove;
2432         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2433                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2434                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2435     }
2436     if (count > 0) {
2437         /* If last read ended with a partial line that we couldn't parse,
2438            prepend it to the new read and try again. */
2439         if (leftover_len > 0) {
2440             for (i=0; i<leftover_len; i++)
2441               buf[i] = buf[leftover_start + i];
2442         }
2443
2444     /* copy new characters into the buffer */
2445     bp = buf + leftover_len;
2446     buf_len=leftover_len;
2447     for (i=0; i<count; i++)
2448     {
2449         // ignore these
2450         if (data[i] == '\r')
2451             continue;
2452
2453         // join lines split by ICS?
2454         if (!appData.noJoin)
2455         {
2456             /*
2457                 Joining just consists of finding matches against the
2458                 continuation sequence, and discarding that sequence
2459                 if found instead of copying it.  So, until a match
2460                 fails, there's nothing to do since it might be the
2461                 complete sequence, and thus, something we don't want
2462                 copied.
2463             */
2464             if (data[i] == cont_seq[cmatch])
2465             {
2466                 cmatch++;
2467                 if (cmatch == strlen(cont_seq))
2468                 {
2469                     cmatch = 0; // complete match.  just reset the counter
2470
2471                     /*
2472                         it's possible for the ICS to not include the space
2473                         at the end of the last word, making our [correct]
2474                         join operation fuse two separate words.  the server
2475                         does this when the space occurs at the width setting.
2476                     */
2477                     if (!buf_len || buf[buf_len-1] != ' ')
2478                     {
2479                         *bp++ = ' ';
2480                         buf_len++;
2481                     }
2482                 }
2483                 continue;
2484             }
2485             else if (cmatch)
2486             {
2487                 /*
2488                     match failed, so we have to copy what matched before
2489                     falling through and copying this character.  In reality,
2490                     this will only ever be just the newline character, but
2491                     it doesn't hurt to be precise.
2492                 */
2493                 strncpy(bp, cont_seq, cmatch);
2494                 bp += cmatch;
2495                 buf_len += cmatch;
2496                 cmatch = 0;
2497             }
2498         }
2499
2500         // copy this char
2501         *bp++ = data[i];
2502         buf_len++;
2503     }
2504
2505         buf[buf_len] = NULLCHAR;
2506 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2507         next_out = 0;
2508         leftover_start = 0;
2509
2510         i = 0;
2511         while (i < buf_len) {
2512             /* Deal with part of the TELNET option negotiation
2513                protocol.  We refuse to do anything beyond the
2514                defaults, except that we allow the WILL ECHO option,
2515                which ICS uses to turn off password echoing when we are
2516                directly connected to it.  We reject this option
2517                if localLineEditing mode is on (always on in xboard)
2518                and we are talking to port 23, which might be a real
2519                telnet server that will try to keep WILL ECHO on permanently.
2520              */
2521             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2522                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2523                 unsigned char option;
2524                 oldi = i;
2525                 switch ((unsigned char) buf[++i]) {
2526                   case TN_WILL:
2527                     if (appData.debugMode)
2528                       fprintf(debugFP, "\n<WILL ");
2529                     switch (option = (unsigned char) buf[++i]) {
2530                       case TN_ECHO:
2531                         if (appData.debugMode)
2532                           fprintf(debugFP, "ECHO ");
2533                         /* Reply only if this is a change, according
2534                            to the protocol rules. */
2535                         if (remoteEchoOption) break;
2536                         if (appData.localLineEditing &&
2537                             atoi(appData.icsPort) == TN_PORT) {
2538                             TelnetRequest(TN_DONT, TN_ECHO);
2539                         } else {
2540                             EchoOff();
2541                             TelnetRequest(TN_DO, TN_ECHO);
2542                             remoteEchoOption = TRUE;
2543                         }
2544                         break;
2545                       default:
2546                         if (appData.debugMode)
2547                           fprintf(debugFP, "%d ", option);
2548                         /* Whatever this is, we don't want it. */
2549                         TelnetRequest(TN_DONT, option);
2550                         break;
2551                     }
2552                     break;
2553                   case TN_WONT:
2554                     if (appData.debugMode)
2555                       fprintf(debugFP, "\n<WONT ");
2556                     switch (option = (unsigned char) buf[++i]) {
2557                       case TN_ECHO:
2558                         if (appData.debugMode)
2559                           fprintf(debugFP, "ECHO ");
2560                         /* Reply only if this is a change, according
2561                            to the protocol rules. */
2562                         if (!remoteEchoOption) break;
2563                         EchoOn();
2564                         TelnetRequest(TN_DONT, TN_ECHO);
2565                         remoteEchoOption = FALSE;
2566                         break;
2567                       default:
2568                         if (appData.debugMode)
2569                           fprintf(debugFP, "%d ", (unsigned char) option);
2570                         /* Whatever this is, it must already be turned
2571                            off, because we never agree to turn on
2572                            anything non-default, so according to the
2573                            protocol rules, we don't reply. */
2574                         break;
2575                     }
2576                     break;
2577                   case TN_DO:
2578                     if (appData.debugMode)
2579                       fprintf(debugFP, "\n<DO ");
2580                     switch (option = (unsigned char) buf[++i]) {
2581                       default:
2582                         /* Whatever this is, we refuse to do it. */
2583                         if (appData.debugMode)
2584                           fprintf(debugFP, "%d ", option);
2585                         TelnetRequest(TN_WONT, option);
2586                         break;
2587                     }
2588                     break;
2589                   case TN_DONT:
2590                     if (appData.debugMode)
2591                       fprintf(debugFP, "\n<DONT ");
2592                     switch (option = (unsigned char) buf[++i]) {
2593                       default:
2594                         if (appData.debugMode)
2595                           fprintf(debugFP, "%d ", option);
2596                         /* Whatever this is, we are already not doing
2597                            it, because we never agree to do anything
2598                            non-default, so according to the protocol
2599                            rules, we don't reply. */
2600                         break;
2601                     }
2602                     break;
2603                   case TN_IAC:
2604                     if (appData.debugMode)
2605                       fprintf(debugFP, "\n<IAC ");
2606                     /* Doubled IAC; pass it through */
2607                     i--;
2608                     break;
2609                   default:
2610                     if (appData.debugMode)
2611                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2612                     /* Drop all other telnet commands on the floor */
2613                     break;
2614                 }
2615                 if (oldi > next_out)
2616                   SendToPlayer(&buf[next_out], oldi - next_out);
2617                 if (++i > next_out)
2618                   next_out = i;
2619                 continue;
2620             }
2621
2622             /* OK, this at least will *usually* work */
2623             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2624                 loggedOn = TRUE;
2625             }
2626
2627             if (loggedOn && !intfSet) {
2628                 if (ics_type == ICS_ICC) {
2629                   snprintf(str, MSG_SIZ,
2630                           "/set-quietly interface %s\n/set-quietly style 12\n",
2631                           programVersion);
2632                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2633                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2634                 } else if (ics_type == ICS_CHESSNET) {
2635                   snprintf(str, MSG_SIZ, "/style 12\n");
2636                 } else {
2637                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2638                   strcat(str, programVersion);
2639                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2640                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2641                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2642 #ifdef WIN32
2643                   strcat(str, "$iset nohighlight 1\n");
2644 #endif
2645                   strcat(str, "$iset lock 1\n$style 12\n");
2646                 }
2647                 SendToICS(str);
2648                 NotifyFrontendLogin();
2649                 intfSet = TRUE;
2650             }
2651
2652             if (started == STARTED_COMMENT) {
2653                 /* Accumulate characters in comment */
2654                 parse[parse_pos++] = buf[i];
2655                 if (buf[i] == '\n') {
2656                     parse[parse_pos] = NULLCHAR;
2657                     if(chattingPartner>=0) {
2658                         char mess[MSG_SIZ];
2659                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2660                         OutputChatMessage(chattingPartner, mess);
2661                         chattingPartner = -1;
2662                         next_out = i+1; // [HGM] suppress printing in ICS window
2663                     } else
2664                     if(!suppressKibitz) // [HGM] kibitz
2665                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2666                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2667                         int nrDigit = 0, nrAlph = 0, j;
2668                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2669                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2670                         parse[parse_pos] = NULLCHAR;
2671                         // try to be smart: if it does not look like search info, it should go to
2672                         // ICS interaction window after all, not to engine-output window.
2673                         for(j=0; j<parse_pos; j++) { // count letters and digits
2674                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2675                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2676                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2677                         }
2678                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2679                             int depth=0; float score;
2680                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2681                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2682                                 pvInfoList[forwardMostMove-1].depth = depth;
2683                                 pvInfoList[forwardMostMove-1].score = 100*score;
2684                             }
2685                             OutputKibitz(suppressKibitz, parse);
2686                         } else {
2687                             char tmp[MSG_SIZ];
2688                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2689                             SendToPlayer(tmp, strlen(tmp));
2690                         }
2691                         next_out = i+1; // [HGM] suppress printing in ICS window
2692                     }
2693                     started = STARTED_NONE;
2694                 } else {
2695                     /* Don't match patterns against characters in comment */
2696                     i++;
2697                     continue;
2698                 }
2699             }
2700             if (started == STARTED_CHATTER) {
2701                 if (buf[i] != '\n') {
2702                     /* Don't match patterns against characters in chatter */
2703                     i++;
2704                     continue;
2705                 }
2706                 started = STARTED_NONE;
2707                 if(suppressKibitz) next_out = i+1;
2708             }
2709
2710             /* Kludge to deal with rcmd protocol */
2711             if (firstTime && looking_at(buf, &i, "\001*")) {
2712                 DisplayFatalError(&buf[1], 0, 1);
2713                 continue;
2714             } else {
2715                 firstTime = FALSE;
2716             }
2717
2718             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2719                 ics_type = ICS_ICC;
2720                 ics_prefix = "/";
2721                 if (appData.debugMode)
2722                   fprintf(debugFP, "ics_type %d\n", ics_type);
2723                 continue;
2724             }
2725             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2726                 ics_type = ICS_FICS;
2727                 ics_prefix = "$";
2728                 if (appData.debugMode)
2729                   fprintf(debugFP, "ics_type %d\n", ics_type);
2730                 continue;
2731             }
2732             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2733                 ics_type = ICS_CHESSNET;
2734                 ics_prefix = "/";
2735                 if (appData.debugMode)
2736                   fprintf(debugFP, "ics_type %d\n", ics_type);
2737                 continue;
2738             }
2739
2740             if (!loggedOn &&
2741                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2742                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2743                  looking_at(buf, &i, "will be \"*\""))) {
2744               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2745               continue;
2746             }
2747
2748             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2749               char buf[MSG_SIZ];
2750               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2751               DisplayIcsInteractionTitle(buf);
2752               have_set_title = TRUE;
2753             }
2754
2755             /* skip finger notes */
2756             if (started == STARTED_NONE &&
2757                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2758                  (buf[i] == '1' && buf[i+1] == '0')) &&
2759                 buf[i+2] == ':' && buf[i+3] == ' ') {
2760               started = STARTED_CHATTER;
2761               i += 3;
2762               continue;
2763             }
2764
2765             oldi = i;
2766             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2767             if(appData.seekGraph) {
2768                 if(soughtPending && MatchSoughtLine(buf+i)) {
2769                     i = strstr(buf+i, "rated") - buf;
2770                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2771                     next_out = leftover_start = i;
2772                     started = STARTED_CHATTER;
2773                     suppressKibitz = TRUE;
2774                     continue;
2775                 }
2776                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2777                         && looking_at(buf, &i, "* ads displayed")) {
2778                     soughtPending = FALSE;
2779                     seekGraphUp = TRUE;
2780                     DrawSeekGraph();
2781                     continue;
2782                 }
2783                 if(appData.autoRefresh) {
2784                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2785                         int s = (ics_type == ICS_ICC); // ICC format differs
2786                         if(seekGraphUp)
2787                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2788                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2789                         looking_at(buf, &i, "*% "); // eat prompt
2790                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2791                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2792                         next_out = i; // suppress
2793                         continue;
2794                     }
2795                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2796                         char *p = star_match[0];
2797                         while(*p) {
2798                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2799                             while(*p && *p++ != ' '); // next
2800                         }
2801                         looking_at(buf, &i, "*% "); // eat prompt
2802                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2803                         next_out = i;
2804                         continue;
2805                     }
2806                 }
2807             }
2808
2809             /* skip formula vars */
2810             if (started == STARTED_NONE &&
2811                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2812               started = STARTED_CHATTER;
2813               i += 3;
2814               continue;
2815             }
2816
2817             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2818             if (appData.autoKibitz && started == STARTED_NONE &&
2819                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2820                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2821                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2822                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2823                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2824                         suppressKibitz = TRUE;
2825                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2826                         next_out = i;
2827                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2828                                 && (gameMode == IcsPlayingWhite)) ||
2829                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2830                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2831                             started = STARTED_CHATTER; // own kibitz we simply discard
2832                         else {
2833                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2834                             parse_pos = 0; parse[0] = NULLCHAR;
2835                             savingComment = TRUE;
2836                             suppressKibitz = gameMode != IcsObserving ? 2 :
2837                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2838                         }
2839                         continue;
2840                 } else
2841                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2842                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2843                          && atoi(star_match[0])) {
2844                     // suppress the acknowledgements of our own autoKibitz
2845                     char *p;
2846                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2847                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2848                     SendToPlayer(star_match[0], strlen(star_match[0]));
2849                     if(looking_at(buf, &i, "*% ")) // eat prompt
2850                         suppressKibitz = FALSE;
2851                     next_out = i;
2852                     continue;
2853                 }
2854             } // [HGM] kibitz: end of patch
2855
2856             // [HGM] chat: intercept tells by users for which we have an open chat window
2857             channel = -1;
2858             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2859                                            looking_at(buf, &i, "* whispers:") ||
2860                                            looking_at(buf, &i, "* kibitzes:") ||
2861                                            looking_at(buf, &i, "* shouts:") ||
2862                                            looking_at(buf, &i, "* c-shouts:") ||
2863                                            looking_at(buf, &i, "--> * ") ||
2864                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2865                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2866                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2867                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2868                 int p;
2869                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2870                 chattingPartner = -1;
2871
2872                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2873                 for(p=0; p<MAX_CHAT; p++) {
2874                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2875                     talker[0] = '['; strcat(talker, "] ");
2876                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2877                     chattingPartner = p; break;
2878                     }
2879                 } else
2880                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2881                 for(p=0; p<MAX_CHAT; p++) {
2882                     if(!strcmp("kibitzes", chatPartner[p])) {
2883                         talker[0] = '['; strcat(talker, "] ");
2884                         chattingPartner = p; break;
2885                     }
2886                 } else
2887                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2888                 for(p=0; p<MAX_CHAT; p++) {
2889                     if(!strcmp("whispers", chatPartner[p])) {
2890                         talker[0] = '['; strcat(talker, "] ");
2891                         chattingPartner = p; break;
2892                     }
2893                 } else
2894                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2895                   if(buf[i-8] == '-' && buf[i-3] == 't')
2896                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2897                     if(!strcmp("c-shouts", chatPartner[p])) {
2898                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2899                         chattingPartner = p; break;
2900                     }
2901                   }
2902                   if(chattingPartner < 0)
2903                   for(p=0; p<MAX_CHAT; p++) {
2904                     if(!strcmp("shouts", chatPartner[p])) {
2905                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2906                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2907                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2908                         chattingPartner = p; break;
2909                     }
2910                   }
2911                 }
2912                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2913                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2914                     talker[0] = 0; Colorize(ColorTell, FALSE);
2915                     chattingPartner = p; break;
2916                 }
2917                 if(chattingPartner<0) i = oldi; else {
2918                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2919                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2920                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2921                     started = STARTED_COMMENT;
2922                     parse_pos = 0; parse[0] = NULLCHAR;
2923                     savingComment = 3 + chattingPartner; // counts as TRUE
2924                     suppressKibitz = TRUE;
2925                     continue;
2926                 }
2927             } // [HGM] chat: end of patch
2928
2929             if (appData.zippyTalk || appData.zippyPlay) {
2930                 /* [DM] Backup address for color zippy lines */
2931                 backup = i;
2932 #if ZIPPY
2933                if (loggedOn == TRUE)
2934                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2935                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2936 #endif
2937             } // [DM] 'else { ' deleted
2938                 if (
2939                     /* Regular tells and says */
2940                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2941                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2942                     looking_at(buf, &i, "* says: ") ||
2943                     /* Don't color "message" or "messages" output */
2944                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2945                     looking_at(buf, &i, "*. * at *:*: ") ||
2946                     looking_at(buf, &i, "--* (*:*): ") ||
2947                     /* Message notifications (same color as tells) */
2948                     looking_at(buf, &i, "* has left a message ") ||
2949                     looking_at(buf, &i, "* just sent you a message:\n") ||
2950                     /* Whispers and kibitzes */
2951                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2952                     looking_at(buf, &i, "* kibitzes: ") ||
2953                     /* Channel tells */
2954                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2955
2956                   if (tkind == 1 && strchr(star_match[0], ':')) {
2957                       /* Avoid "tells you:" spoofs in channels */
2958                      tkind = 3;
2959                   }
2960                   if (star_match[0][0] == NULLCHAR ||
2961                       strchr(star_match[0], ' ') ||
2962                       (tkind == 3 && strchr(star_match[1], ' '))) {
2963                     /* Reject bogus matches */
2964                     i = oldi;
2965                   } else {
2966                     if (appData.colorize) {
2967                       if (oldi > next_out) {
2968                         SendToPlayer(&buf[next_out], oldi - next_out);
2969                         next_out = oldi;
2970                       }
2971                       switch (tkind) {
2972                       case 1:
2973                         Colorize(ColorTell, FALSE);
2974                         curColor = ColorTell;
2975                         break;
2976                       case 2:
2977                         Colorize(ColorKibitz, FALSE);
2978                         curColor = ColorKibitz;
2979                         break;
2980                       case 3:
2981                         p = strrchr(star_match[1], '(');
2982                         if (p == NULL) {
2983                           p = star_match[1];
2984                         } else {
2985                           p++;
2986                         }
2987                         if (atoi(p) == 1) {
2988                           Colorize(ColorChannel1, FALSE);
2989                           curColor = ColorChannel1;
2990                         } else {
2991                           Colorize(ColorChannel, FALSE);
2992                           curColor = ColorChannel;
2993                         }
2994                         break;
2995                       case 5:
2996                         curColor = ColorNormal;
2997                         break;
2998                       }
2999                     }
3000                     if (started == STARTED_NONE && appData.autoComment &&
3001                         (gameMode == IcsObserving ||
3002                          gameMode == IcsPlayingWhite ||
3003                          gameMode == IcsPlayingBlack)) {
3004                       parse_pos = i - oldi;
3005                       memcpy(parse, &buf[oldi], parse_pos);
3006                       parse[parse_pos] = NULLCHAR;
3007                       started = STARTED_COMMENT;
3008                       savingComment = TRUE;
3009                     } else {
3010                       started = STARTED_CHATTER;
3011                       savingComment = FALSE;
3012                     }
3013                     loggedOn = TRUE;
3014                     continue;
3015                   }
3016                 }
3017
3018                 if (looking_at(buf, &i, "* s-shouts: ") ||
3019                     looking_at(buf, &i, "* c-shouts: ")) {
3020                     if (appData.colorize) {
3021                         if (oldi > next_out) {
3022                             SendToPlayer(&buf[next_out], oldi - next_out);
3023                             next_out = oldi;
3024                         }
3025                         Colorize(ColorSShout, FALSE);
3026                         curColor = ColorSShout;
3027                     }
3028                     loggedOn = TRUE;
3029                     started = STARTED_CHATTER;
3030                     continue;
3031                 }
3032
3033                 if (looking_at(buf, &i, "--->")) {
3034                     loggedOn = TRUE;
3035                     continue;
3036                 }
3037
3038                 if (looking_at(buf, &i, "* shouts: ") ||
3039                     looking_at(buf, &i, "--> ")) {
3040                     if (appData.colorize) {
3041                         if (oldi > next_out) {
3042                             SendToPlayer(&buf[next_out], oldi - next_out);
3043                             next_out = oldi;
3044                         }
3045                         Colorize(ColorShout, FALSE);
3046                         curColor = ColorShout;
3047                     }
3048                     loggedOn = TRUE;
3049                     started = STARTED_CHATTER;
3050                     continue;
3051                 }
3052
3053                 if (looking_at( buf, &i, "Challenge:")) {
3054                     if (appData.colorize) {
3055                         if (oldi > next_out) {
3056                             SendToPlayer(&buf[next_out], oldi - next_out);
3057                             next_out = oldi;
3058                         }
3059                         Colorize(ColorChallenge, FALSE);
3060                         curColor = ColorChallenge;
3061                     }
3062                     loggedOn = TRUE;
3063                     continue;
3064                 }
3065
3066                 if (looking_at(buf, &i, "* offers you") ||
3067                     looking_at(buf, &i, "* offers to be") ||
3068                     looking_at(buf, &i, "* would like to") ||
3069                     looking_at(buf, &i, "* requests to") ||
3070                     looking_at(buf, &i, "Your opponent offers") ||
3071                     looking_at(buf, &i, "Your opponent requests")) {
3072
3073                     if (appData.colorize) {
3074                         if (oldi > next_out) {
3075                             SendToPlayer(&buf[next_out], oldi - next_out);
3076                             next_out = oldi;
3077                         }
3078                         Colorize(ColorRequest, FALSE);
3079                         curColor = ColorRequest;
3080                     }
3081                     continue;
3082                 }
3083
3084                 if (looking_at(buf, &i, "* (*) seeking")) {
3085                     if (appData.colorize) {
3086                         if (oldi > next_out) {
3087                             SendToPlayer(&buf[next_out], oldi - next_out);
3088                             next_out = oldi;
3089                         }
3090                         Colorize(ColorSeek, FALSE);
3091                         curColor = ColorSeek;
3092                     }
3093                     continue;
3094             }
3095
3096             if (looking_at(buf, &i, "\\   ")) {
3097                 if (prevColor != ColorNormal) {
3098                     if (oldi > next_out) {
3099                         SendToPlayer(&buf[next_out], oldi - next_out);
3100                         next_out = oldi;
3101                     }
3102                     Colorize(prevColor, TRUE);
3103                     curColor = prevColor;
3104                 }
3105                 if (savingComment) {
3106                     parse_pos = i - oldi;
3107                     memcpy(parse, &buf[oldi], parse_pos);
3108                     parse[parse_pos] = NULLCHAR;
3109                     started = STARTED_COMMENT;
3110                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3111                         chattingPartner = savingComment - 3; // kludge to remember the box
3112                 } else {
3113                     started = STARTED_CHATTER;
3114                 }
3115                 continue;
3116             }
3117
3118             if (looking_at(buf, &i, "Black Strength :") ||
3119                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3120                 looking_at(buf, &i, "<10>") ||
3121                 looking_at(buf, &i, "#@#")) {
3122                 /* Wrong board style */
3123                 loggedOn = TRUE;
3124                 SendToICS(ics_prefix);
3125                 SendToICS("set style 12\n");
3126                 SendToICS(ics_prefix);
3127                 SendToICS("refresh\n");
3128                 continue;
3129             }
3130
3131             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3132                 ICSInitScript();
3133                 have_sent_ICS_logon = 1;
3134                 continue;
3135             }
3136
3137             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3138                 (looking_at(buf, &i, "\n<12> ") ||
3139                  looking_at(buf, &i, "<12> "))) {
3140                 loggedOn = TRUE;
3141                 if (oldi > next_out) {
3142                     SendToPlayer(&buf[next_out], oldi - next_out);
3143                 }
3144                 next_out = i;
3145                 started = STARTED_BOARD;
3146                 parse_pos = 0;
3147                 continue;
3148             }
3149
3150             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3151                 looking_at(buf, &i, "<b1> ")) {
3152                 if (oldi > next_out) {
3153                     SendToPlayer(&buf[next_out], oldi - next_out);
3154                 }
3155                 next_out = i;
3156                 started = STARTED_HOLDINGS;
3157                 parse_pos = 0;
3158                 continue;
3159             }
3160
3161             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3162                 loggedOn = TRUE;
3163                 /* Header for a move list -- first line */
3164
3165                 switch (ics_getting_history) {
3166                   case H_FALSE:
3167                     switch (gameMode) {
3168                       case IcsIdle:
3169                       case BeginningOfGame:
3170                         /* User typed "moves" or "oldmoves" while we
3171                            were idle.  Pretend we asked for these
3172                            moves and soak them up so user can step
3173                            through them and/or save them.
3174                            */
3175                         Reset(FALSE, TRUE);
3176                         gameMode = IcsObserving;
3177                         ModeHighlight();
3178                         ics_gamenum = -1;
3179                         ics_getting_history = H_GOT_UNREQ_HEADER;
3180                         break;
3181                       case EditGame: /*?*/
3182                       case EditPosition: /*?*/
3183                         /* Should above feature work in these modes too? */
3184                         /* For now it doesn't */
3185                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3186                         break;
3187                       default:
3188                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3189                         break;
3190                     }
3191                     break;
3192                   case H_REQUESTED:
3193                     /* Is this the right one? */
3194                     if (gameInfo.white && gameInfo.black &&
3195                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3196                         strcmp(gameInfo.black, star_match[2]) == 0) {
3197                         /* All is well */
3198                         ics_getting_history = H_GOT_REQ_HEADER;
3199                     }
3200                     break;
3201                   case H_GOT_REQ_HEADER:
3202                   case H_GOT_UNREQ_HEADER:
3203                   case H_GOT_UNWANTED_HEADER:
3204                   case H_GETTING_MOVES:
3205                     /* Should not happen */
3206                     DisplayError(_("Error gathering move list: two headers"), 0);
3207                     ics_getting_history = H_FALSE;
3208                     break;
3209                 }
3210
3211                 /* Save player ratings into gameInfo if needed */
3212                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3213                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3214                     (gameInfo.whiteRating == -1 ||
3215                      gameInfo.blackRating == -1)) {
3216
3217                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3218                     gameInfo.blackRating = string_to_rating(star_match[3]);
3219                     if (appData.debugMode)
3220                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3221                               gameInfo.whiteRating, gameInfo.blackRating);
3222                 }
3223                 continue;
3224             }
3225
3226             if (looking_at(buf, &i,
3227               "* * match, initial time: * minute*, increment: * second")) {
3228                 /* Header for a move list -- second line */
3229                 /* Initial board will follow if this is a wild game */
3230                 if (gameInfo.event != NULL) free(gameInfo.event);
3231                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3232                 gameInfo.event = StrSave(str);
3233                 /* [HGM] we switched variant. Translate boards if needed. */
3234                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3235                 continue;
3236             }
3237
3238             if (looking_at(buf, &i, "Move  ")) {
3239                 /* Beginning of a move list */
3240                 switch (ics_getting_history) {
3241                   case H_FALSE:
3242                     /* Normally should not happen */
3243                     /* Maybe user hit reset while we were parsing */
3244                     break;
3245                   case H_REQUESTED:
3246                     /* Happens if we are ignoring a move list that is not
3247                      * the one we just requested.  Common if the user
3248                      * tries to observe two games without turning off
3249                      * getMoveList */
3250                     break;
3251                   case H_GETTING_MOVES:
3252                     /* Should not happen */
3253                     DisplayError(_("Error gathering move list: nested"), 0);
3254                     ics_getting_history = H_FALSE;
3255                     break;
3256                   case H_GOT_REQ_HEADER:
3257                     ics_getting_history = H_GETTING_MOVES;
3258                     started = STARTED_MOVES;
3259                     parse_pos = 0;
3260                     if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                     }
3263                     break;
3264                   case H_GOT_UNREQ_HEADER:
3265                     ics_getting_history = H_GETTING_MOVES;
3266                     started = STARTED_MOVES_NOHIDE;
3267                     parse_pos = 0;
3268                     break;
3269                   case H_GOT_UNWANTED_HEADER:
3270                     ics_getting_history = H_FALSE;
3271                     break;
3272                 }
3273                 continue;
3274             }
3275
3276             if (looking_at(buf, &i, "% ") ||
3277                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3278                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3279                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3280                     soughtPending = FALSE;
3281                     seekGraphUp = TRUE;
3282                     DrawSeekGraph();
3283                 }
3284                 if(suppressKibitz) next_out = i;
3285                 savingComment = FALSE;
3286                 suppressKibitz = 0;
3287                 switch (started) {
3288                   case STARTED_MOVES:
3289                   case STARTED_MOVES_NOHIDE:
3290                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3291                     parse[parse_pos + i - oldi] = NULLCHAR;
3292                     ParseGameHistory(parse);
3293 #if ZIPPY
3294                     if (appData.zippyPlay && first.initDone) {
3295                         FeedMovesToProgram(&first, forwardMostMove);
3296                         if (gameMode == IcsPlayingWhite) {
3297                             if (WhiteOnMove(forwardMostMove)) {
3298                                 if (first.sendTime) {
3299                                   if (first.useColors) {
3300                                     SendToProgram("black\n", &first);
3301                                   }
3302                                   SendTimeRemaining(&first, TRUE);
3303                                 }
3304                                 if (first.useColors) {
3305                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3306                                 }
3307                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3308                                 first.maybeThinking = TRUE;
3309                             } else {
3310                                 if (first.usePlayother) {
3311                                   if (first.sendTime) {
3312                                     SendTimeRemaining(&first, TRUE);
3313                                   }
3314                                   SendToProgram("playother\n", &first);
3315                                   firstMove = FALSE;
3316                                 } else {
3317                                   firstMove = TRUE;
3318                                 }
3319                             }
3320                         } else if (gameMode == IcsPlayingBlack) {
3321                             if (!WhiteOnMove(forwardMostMove)) {
3322                                 if (first.sendTime) {
3323                                   if (first.useColors) {
3324                                     SendToProgram("white\n", &first);
3325                                   }
3326                                   SendTimeRemaining(&first, FALSE);
3327                                 }
3328                                 if (first.useColors) {
3329                                   SendToProgram("black\n", &first);
3330                                 }
3331                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3332                                 first.maybeThinking = TRUE;
3333                             } else {
3334                                 if (first.usePlayother) {
3335                                   if (first.sendTime) {
3336                                     SendTimeRemaining(&first, FALSE);
3337                                   }
3338                                   SendToProgram("playother\n", &first);
3339                                   firstMove = FALSE;
3340                                 } else {
3341                                   firstMove = TRUE;
3342                                 }
3343                             }
3344                         }
3345                     }
3346 #endif
3347                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3348                         /* Moves came from oldmoves or moves command
3349                            while we weren't doing anything else.
3350                            */
3351                         currentMove = forwardMostMove;
3352                         ClearHighlights();/*!!could figure this out*/
3353                         flipView = appData.flipView;
3354                         DrawPosition(TRUE, boards[currentMove]);
3355                         DisplayBothClocks();
3356                         snprintf(str, MSG_SIZ, "%s vs. %s",
3357                                 gameInfo.white, gameInfo.black);
3358                         DisplayTitle(str);
3359                         gameMode = IcsIdle;
3360                     } else {
3361                         /* Moves were history of an active game */
3362                         if (gameInfo.resultDetails != NULL) {
3363                             free(gameInfo.resultDetails);
3364                             gameInfo.resultDetails = NULL;
3365                         }
3366                     }
3367                     HistorySet(parseList, backwardMostMove,
3368                                forwardMostMove, currentMove-1);
3369                     DisplayMove(currentMove - 1);
3370                     if (started == STARTED_MOVES) next_out = i;
3371                     started = STARTED_NONE;
3372                     ics_getting_history = H_FALSE;
3373                     break;
3374
3375                   case STARTED_OBSERVE:
3376                     started = STARTED_NONE;
3377                     SendToICS(ics_prefix);
3378                     SendToICS("refresh\n");
3379                     break;
3380
3381                   default:
3382                     break;
3383                 }
3384                 if(bookHit) { // [HGM] book: simulate book reply
3385                     static char bookMove[MSG_SIZ]; // a bit generous?
3386
3387                     programStats.nodes = programStats.depth = programStats.time =
3388                     programStats.score = programStats.got_only_move = 0;
3389                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3390
3391                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3392                     strcat(bookMove, bookHit);
3393                     HandleMachineMove(bookMove, &first);
3394                 }
3395                 continue;
3396             }
3397
3398             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3399                  started == STARTED_HOLDINGS ||
3400                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3401                 /* Accumulate characters in move list or board */
3402                 parse[parse_pos++] = buf[i];
3403             }
3404
3405             /* Start of game messages.  Mostly we detect start of game
3406                when the first board image arrives.  On some versions
3407                of the ICS, though, we need to do a "refresh" after starting
3408                to observe in order to get the current board right away. */
3409             if (looking_at(buf, &i, "Adding game * to observation list")) {
3410                 started = STARTED_OBSERVE;
3411                 continue;
3412             }
3413
3414             /* Handle auto-observe */
3415             if (appData.autoObserve &&
3416                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3417                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3418                 char *player;
3419                 /* Choose the player that was highlighted, if any. */
3420                 if (star_match[0][0] == '\033' ||
3421                     star_match[1][0] != '\033') {
3422                     player = star_match[0];
3423                 } else {
3424                     player = star_match[2];
3425                 }
3426                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3427                         ics_prefix, StripHighlightAndTitle(player));
3428                 SendToICS(str);
3429
3430                 /* Save ratings from notify string */
3431                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3432                 player1Rating = string_to_rating(star_match[1]);
3433                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3434                 player2Rating = string_to_rating(star_match[3]);
3435
3436                 if (appData.debugMode)
3437                   fprintf(debugFP,
3438                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3439                           player1Name, player1Rating,
3440                           player2Name, player2Rating);
3441
3442                 continue;
3443             }
3444
3445             /* Deal with automatic examine mode after a game,
3446                and with IcsObserving -> IcsExamining transition */
3447             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3448                 looking_at(buf, &i, "has made you an examiner of game *")) {
3449
3450                 int gamenum = atoi(star_match[0]);
3451                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3452                     gamenum == ics_gamenum) {
3453                     /* We were already playing or observing this game;
3454                        no need to refetch history */
3455                     gameMode = IcsExamining;
3456                     if (pausing) {
3457                         pauseExamForwardMostMove = forwardMostMove;
3458                     } else if (currentMove < forwardMostMove) {
3459                         ForwardInner(forwardMostMove);
3460                     }
3461                 } else {
3462                     /* I don't think this case really can happen */
3463                     SendToICS(ics_prefix);
3464                     SendToICS("refresh\n");
3465                 }
3466                 continue;
3467             }
3468
3469             /* Error messages */
3470 //          if (ics_user_moved) {
3471             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3472                 if (looking_at(buf, &i, "Illegal move") ||
3473                     looking_at(buf, &i, "Not a legal move") ||
3474                     looking_at(buf, &i, "Your king is in check") ||
3475                     looking_at(buf, &i, "It isn't your turn") ||
3476                     looking_at(buf, &i, "It is not your move")) {
3477                     /* Illegal move */
3478                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3479                         currentMove = forwardMostMove-1;
3480                         DisplayMove(currentMove - 1); /* before DMError */
3481                         DrawPosition(FALSE, boards[currentMove]);
3482                         SwitchClocks(forwardMostMove-1); // [HGM] race
3483                         DisplayBothClocks();
3484                     }
3485                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3486                     ics_user_moved = 0;
3487                     continue;
3488                 }
3489             }
3490
3491             if (looking_at(buf, &i, "still have time") ||
3492                 looking_at(buf, &i, "not out of time") ||
3493                 looking_at(buf, &i, "either player is out of time") ||
3494                 looking_at(buf, &i, "has timeseal; checking")) {
3495                 /* We must have called his flag a little too soon */
3496                 whiteFlag = blackFlag = FALSE;
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i, "added * seconds to") ||
3501                 looking_at(buf, &i, "seconds were added to")) {
3502                 /* Update the clocks */
3503                 SendToICS(ics_prefix);
3504                 SendToICS("refresh\n");
3505                 continue;
3506             }
3507
3508             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3509                 ics_clock_paused = TRUE;
3510                 StopClocks();
3511                 continue;
3512             }
3513
3514             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3515                 ics_clock_paused = FALSE;
3516                 StartClocks();
3517                 continue;
3518             }
3519
3520             /* Grab player ratings from the Creating: message.
3521                Note we have to check for the special case when
3522                the ICS inserts things like [white] or [black]. */
3523             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3524                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3525                 /* star_matches:
3526                    0    player 1 name (not necessarily white)
3527                    1    player 1 rating
3528                    2    empty, white, or black (IGNORED)
3529                    3    player 2 name (not necessarily black)
3530                    4    player 2 rating
3531
3532                    The names/ratings are sorted out when the game
3533                    actually starts (below).
3534                 */
3535                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3536                 player1Rating = string_to_rating(star_match[1]);
3537                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3538                 player2Rating = string_to_rating(star_match[4]);
3539
3540                 if (appData.debugMode)
3541                   fprintf(debugFP,
3542                           "Ratings from 'Creating:' %s %d, %s %d\n",
3543                           player1Name, player1Rating,
3544                           player2Name, player2Rating);
3545
3546                 continue;
3547             }
3548
3549             /* Improved generic start/end-of-game messages */
3550             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3551                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3552                 /* If tkind == 0: */
3553                 /* star_match[0] is the game number */
3554                 /*           [1] is the white player's name */
3555                 /*           [2] is the black player's name */
3556                 /* For end-of-game: */
3557                 /*           [3] is the reason for the game end */
3558                 /*           [4] is a PGN end game-token, preceded by " " */
3559                 /* For start-of-game: */
3560                 /*           [3] begins with "Creating" or "Continuing" */
3561                 /*           [4] is " *" or empty (don't care). */
3562                 int gamenum = atoi(star_match[0]);
3563                 char *whitename, *blackname, *why, *endtoken;
3564                 ChessMove endtype = EndOfFile;
3565
3566                 if (tkind == 0) {
3567                   whitename = star_match[1];
3568                   blackname = star_match[2];
3569                   why = star_match[3];
3570                   endtoken = star_match[4];
3571                 } else {
3572                   whitename = star_match[1];
3573                   blackname = star_match[3];
3574                   why = star_match[5];
3575                   endtoken = star_match[6];
3576                 }
3577
3578                 /* Game start messages */
3579                 if (strncmp(why, "Creating ", 9) == 0 ||
3580                     strncmp(why, "Continuing ", 11) == 0) {
3581                     gs_gamenum = gamenum;
3582                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3583                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3584 #if ZIPPY
3585                     if (appData.zippyPlay) {
3586                         ZippyGameStart(whitename, blackname);
3587                     }
3588 #endif /*ZIPPY*/
3589                     partnerBoardValid = FALSE; // [HGM] bughouse
3590                     continue;
3591                 }
3592
3593                 /* Game end messages */
3594                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3595                     ics_gamenum != gamenum) {
3596                     continue;
3597                 }
3598                 while (endtoken[0] == ' ') endtoken++;
3599                 switch (endtoken[0]) {
3600                   case '*':
3601                   default:
3602                     endtype = GameUnfinished;
3603                     break;
3604                   case '0':
3605                     endtype = BlackWins;
3606                     break;
3607                   case '1':
3608                     if (endtoken[1] == '/')
3609                       endtype = GameIsDrawn;
3610                     else
3611                       endtype = WhiteWins;
3612                     break;
3613                 }
3614                 GameEnds(endtype, why, GE_ICS);
3615 #if ZIPPY
3616                 if (appData.zippyPlay && first.initDone) {
3617                     ZippyGameEnd(endtype, why);
3618                     if (first.pr == NULL) {
3619                       /* Start the next process early so that we'll
3620                          be ready for the next challenge */
3621                       StartChessProgram(&first);
3622                     }
3623                     /* Send "new" early, in case this command takes
3624                        a long time to finish, so that we'll be ready
3625                        for the next challenge. */
3626                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3627                     Reset(TRUE, TRUE);
3628                 }
3629 #endif /*ZIPPY*/
3630                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3631                 continue;
3632             }
3633
3634             if (looking_at(buf, &i, "Removing game * from observation") ||
3635                 looking_at(buf, &i, "no longer observing game *") ||
3636                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3637                 if (gameMode == IcsObserving &&
3638                     atoi(star_match[0]) == ics_gamenum)
3639                   {
3640                       /* icsEngineAnalyze */
3641                       if (appData.icsEngineAnalyze) {
3642                             ExitAnalyzeMode();
3643                             ModeHighlight();
3644                       }
3645                       StopClocks();
3646                       gameMode = IcsIdle;
3647                       ics_gamenum = -1;
3648                       ics_user_moved = FALSE;
3649                   }
3650                 continue;
3651             }
3652
3653             if (looking_at(buf, &i, "no longer examining game *")) {
3654                 if (gameMode == IcsExamining &&
3655                     atoi(star_match[0]) == ics_gamenum)
3656                   {
3657                       gameMode = IcsIdle;
3658                       ics_gamenum = -1;
3659                       ics_user_moved = FALSE;
3660                   }
3661                 continue;
3662             }
3663
3664             /* Advance leftover_start past any newlines we find,
3665                so only partial lines can get reparsed */
3666             if (looking_at(buf, &i, "\n")) {
3667                 prevColor = curColor;
3668                 if (curColor != ColorNormal) {
3669                     if (oldi > next_out) {
3670                         SendToPlayer(&buf[next_out], oldi - next_out);
3671                         next_out = oldi;
3672                     }
3673                     Colorize(ColorNormal, FALSE);
3674                     curColor = ColorNormal;
3675                 }
3676                 if (started == STARTED_BOARD) {
3677                     started = STARTED_NONE;
3678                     parse[parse_pos] = NULLCHAR;
3679                     ParseBoard12(parse);
3680                     ics_user_moved = 0;
3681
3682                     /* Send premove here */
3683                     if (appData.premove) {
3684                       char str[MSG_SIZ];
3685                       if (currentMove == 0 &&
3686                           gameMode == IcsPlayingWhite &&
3687                           appData.premoveWhite) {
3688                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3689                         if (appData.debugMode)
3690                           fprintf(debugFP, "Sending premove:\n");
3691                         SendToICS(str);
3692                       } else if (currentMove == 1 &&
3693                                  gameMode == IcsPlayingBlack &&
3694                                  appData.premoveBlack) {
3695                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3696                         if (appData.debugMode)
3697                           fprintf(debugFP, "Sending premove:\n");
3698                         SendToICS(str);
3699                       } else if (gotPremove) {
3700                         gotPremove = 0;
3701                         ClearPremoveHighlights();
3702                         if (appData.debugMode)
3703                           fprintf(debugFP, "Sending premove:\n");
3704                           UserMoveEvent(premoveFromX, premoveFromY,
3705                                         premoveToX, premoveToY,
3706                                         premovePromoChar);
3707                       }
3708                     }
3709
3710                     /* Usually suppress following prompt */
3711                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3712                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3713                         if (looking_at(buf, &i, "*% ")) {
3714                             savingComment = FALSE;
3715                             suppressKibitz = 0;
3716                         }
3717                     }
3718                     next_out = i;
3719                 } else if (started == STARTED_HOLDINGS) {
3720                     int gamenum;
3721                     char new_piece[MSG_SIZ];
3722                     started = STARTED_NONE;
3723                     parse[parse_pos] = NULLCHAR;
3724                     if (appData.debugMode)
3725                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3726                                                         parse, currentMove);
3727                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3728                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3729                         if (gameInfo.variant == VariantNormal) {
3730                           /* [HGM] We seem to switch variant during a game!
3731                            * Presumably no holdings were displayed, so we have
3732                            * to move the position two files to the right to
3733                            * create room for them!
3734                            */
3735                           VariantClass newVariant;
3736                           switch(gameInfo.boardWidth) { // base guess on board width
3737                                 case 9:  newVariant = VariantShogi; break;
3738                                 case 10: newVariant = VariantGreat; break;
3739                                 default: newVariant = VariantCrazyhouse; break;
3740                           }
3741                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3742                           /* Get a move list just to see the header, which
3743                              will tell us whether this is really bug or zh */
3744                           if (ics_getting_history == H_FALSE) {
3745                             ics_getting_history = H_REQUESTED;
3746                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3747                             SendToICS(str);
3748                           }
3749                         }
3750                         new_piece[0] = NULLCHAR;
3751                         sscanf(parse, "game %d white [%s black [%s <- %s",
3752                                &gamenum, white_holding, black_holding,
3753                                new_piece);
3754                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3755                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3756                         /* [HGM] copy holdings to board holdings area */
3757                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3758                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3759                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3760 #if ZIPPY
3761                         if (appData.zippyPlay && first.initDone) {
3762                             ZippyHoldings(white_holding, black_holding,
3763                                           new_piece);
3764                         }
3765 #endif /*ZIPPY*/
3766                         if (tinyLayout || smallLayout) {
3767                             char wh[16], bh[16];
3768                             PackHolding(wh, white_holding);
3769                             PackHolding(bh, black_holding);
3770                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3771                                     gameInfo.white, gameInfo.black);
3772                         } else {
3773                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3774                                     gameInfo.white, white_holding,
3775                                     gameInfo.black, black_holding);
3776                         }
3777                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3778                         DrawPosition(FALSE, boards[currentMove]);
3779                         DisplayTitle(str);
3780                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3781                         sscanf(parse, "game %d white [%s black [%s <- %s",
3782                                &gamenum, white_holding, black_holding,
3783                                new_piece);
3784                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3785                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3786                         /* [HGM] copy holdings to partner-board holdings area */
3787                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3788                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3789                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3790                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3791                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3792                       }
3793                     }
3794                     /* Suppress following prompt */
3795                     if (looking_at(buf, &i, "*% ")) {
3796                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3797                         savingComment = FALSE;
3798                         suppressKibitz = 0;
3799                     }
3800                     next_out = i;
3801                 }
3802                 continue;
3803             }
3804
3805             i++;                /* skip unparsed character and loop back */
3806         }
3807
3808         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3809 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3810 //          SendToPlayer(&buf[next_out], i - next_out);
3811             started != STARTED_HOLDINGS && leftover_start > next_out) {
3812             SendToPlayer(&buf[next_out], leftover_start - next_out);
3813             next_out = i;
3814         }
3815
3816         leftover_len = buf_len - leftover_start;
3817         /* if buffer ends with something we couldn't parse,
3818            reparse it after appending the next read */
3819
3820     } else if (count == 0) {
3821         RemoveInputSource(isr);
3822         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3823     } else {
3824         DisplayFatalError(_("Error reading from ICS"), error, 1);
3825     }
3826 }
3827
3828
3829 /* Board style 12 looks like this:
3830
3831    <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
3832
3833  * The "<12> " is stripped before it gets to this routine.  The two
3834  * trailing 0's (flip state and clock ticking) are later addition, and
3835  * some chess servers may not have them, or may have only the first.
3836  * Additional trailing fields may be added in the future.
3837  */
3838
3839 #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"
3840
3841 #define RELATION_OBSERVING_PLAYED    0
3842 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3843 #define RELATION_PLAYING_MYMOVE      1
3844 #define RELATION_PLAYING_NOTMYMOVE  -1
3845 #define RELATION_EXAMINING           2
3846 #define RELATION_ISOLATED_BOARD     -3
3847 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3848
3849 void
3850 ParseBoard12(string)
3851      char *string;
3852 {
3853     GameMode newGameMode;
3854     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3855     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3856     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3857     char to_play, board_chars[200];
3858     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3859     char black[32], white[32];
3860     Board board;
3861     int prevMove = currentMove;
3862     int ticking = 2;
3863     ChessMove moveType;
3864     int fromX, fromY, toX, toY;
3865     char promoChar;
3866     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3867     char *bookHit = NULL; // [HGM] book
3868     Boolean weird = FALSE, reqFlag = FALSE;
3869
3870     fromX = fromY = toX = toY = -1;
3871
3872     newGame = FALSE;
3873
3874     if (appData.debugMode)
3875       fprintf(debugFP, _("Parsing board: %s\n"), string);
3876
3877     move_str[0] = NULLCHAR;
3878     elapsed_time[0] = NULLCHAR;
3879     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3880         int  i = 0, j;
3881         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3882             if(string[i] == ' ') { ranks++; files = 0; }
3883             else files++;
3884             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3885             i++;
3886         }
3887         for(j = 0; j <i; j++) board_chars[j] = string[j];
3888         board_chars[i] = '\0';
3889         string += i + 1;
3890     }
3891     n = sscanf(string, PATTERN, &to_play, &double_push,
3892                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3893                &gamenum, white, black, &relation, &basetime, &increment,
3894                &white_stren, &black_stren, &white_time, &black_time,
3895                &moveNum, str, elapsed_time, move_str, &ics_flip,
3896                &ticking);
3897
3898     if (n < 21) {
3899         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3900         DisplayError(str, 0);
3901         return;
3902     }
3903
3904     /* Convert the move number to internal form */
3905     moveNum = (moveNum - 1) * 2;
3906     if (to_play == 'B') moveNum++;
3907     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3908       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3909                         0, 1);
3910       return;
3911     }
3912
3913     switch (relation) {
3914       case RELATION_OBSERVING_PLAYED:
3915       case RELATION_OBSERVING_STATIC:
3916         if (gamenum == -1) {
3917             /* Old ICC buglet */
3918             relation = RELATION_OBSERVING_STATIC;
3919         }
3920         newGameMode = IcsObserving;
3921         break;
3922       case RELATION_PLAYING_MYMOVE:
3923       case RELATION_PLAYING_NOTMYMOVE:
3924         newGameMode =
3925           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3926             IcsPlayingWhite : IcsPlayingBlack;
3927         break;
3928       case RELATION_EXAMINING:
3929         newGameMode = IcsExamining;
3930         break;
3931       case RELATION_ISOLATED_BOARD:
3932       default:
3933         /* Just display this board.  If user was doing something else,
3934            we will forget about it until the next board comes. */
3935         newGameMode = IcsIdle;
3936         break;
3937       case RELATION_STARTING_POSITION:
3938         newGameMode = gameMode;
3939         break;
3940     }
3941
3942     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3943          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3944       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3945       char *toSqr;
3946       for (k = 0; k < ranks; k++) {
3947         for (j = 0; j < files; j++)
3948           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3949         if(gameInfo.holdingsWidth > 1) {
3950              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3951              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3952         }
3953       }
3954       CopyBoard(partnerBoard, board);
3955       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3956         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3957         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3958       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3959       if(toSqr = strchr(str, '-')) {
3960         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3961         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3962       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3963       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3964       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3965       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3966       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3967       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3968                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3969       DisplayMessage(partnerStatus, "");
3970         partnerBoardValid = TRUE;
3971       return;
3972     }
3973
3974     /* Modify behavior for initial board display on move listing
3975        of wild games.
3976        */
3977     switch (ics_getting_history) {
3978       case H_FALSE:
3979       case H_REQUESTED:
3980         break;
3981       case H_GOT_REQ_HEADER:
3982       case H_GOT_UNREQ_HEADER:
3983         /* This is the initial position of the current game */
3984         gamenum = ics_gamenum;
3985         moveNum = 0;            /* old ICS bug workaround */
3986         if (to_play == 'B') {
3987           startedFromSetupPosition = TRUE;
3988           blackPlaysFirst = TRUE;
3989           moveNum = 1;
3990           if (forwardMostMove == 0) forwardMostMove = 1;
3991           if (backwardMostMove == 0) backwardMostMove = 1;
3992           if (currentMove == 0) currentMove = 1;
3993         }
3994         newGameMode = gameMode;
3995         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3996         break;
3997       case H_GOT_UNWANTED_HEADER:
3998         /* This is an initial board that we don't want */
3999         return;
4000       case H_GETTING_MOVES:
4001         /* Should not happen */
4002         DisplayError(_("Error gathering move list: extra board"), 0);
4003         ics_getting_history = H_FALSE;
4004         return;
4005     }
4006
4007    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4008                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4009      /* [HGM] We seem to have switched variant unexpectedly
4010       * Try to guess new variant from board size
4011       */
4012           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4013           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4014           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4015           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4016           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4017           if(!weird) newVariant = VariantNormal;
4018           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4019           /* Get a move list just to see the header, which
4020              will tell us whether this is really bug or zh */
4021           if (ics_getting_history == H_FALSE) {
4022             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4023             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4024             SendToICS(str);
4025           }
4026     }
4027
4028     /* Take action if this is the first board of a new game, or of a
4029        different game than is currently being displayed.  */
4030     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4031         relation == RELATION_ISOLATED_BOARD) {
4032
4033         /* Forget the old game and get the history (if any) of the new one */
4034         if (gameMode != BeginningOfGame) {
4035           Reset(TRUE, TRUE);
4036         }
4037         newGame = TRUE;
4038         if (appData.autoRaiseBoard) BoardToTop();
4039         prevMove = -3;
4040         if (gamenum == -1) {
4041             newGameMode = IcsIdle;
4042         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4043                    appData.getMoveList && !reqFlag) {
4044             /* Need to get game history */
4045             ics_getting_history = H_REQUESTED;
4046             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4047             SendToICS(str);
4048         }
4049
4050         /* Initially flip the board to have black on the bottom if playing
4051            black or if the ICS flip flag is set, but let the user change
4052            it with the Flip View button. */
4053         flipView = appData.autoFlipView ?
4054           (newGameMode == IcsPlayingBlack) || ics_flip :
4055           appData.flipView;
4056
4057         /* Done with values from previous mode; copy in new ones */
4058         gameMode = newGameMode;
4059         ModeHighlight();
4060         ics_gamenum = gamenum;
4061         if (gamenum == gs_gamenum) {
4062             int klen = strlen(gs_kind);
4063             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4064             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4065             gameInfo.event = StrSave(str);
4066         } else {
4067             gameInfo.event = StrSave("ICS game");
4068         }
4069         gameInfo.site = StrSave(appData.icsHost);
4070         gameInfo.date = PGNDate();
4071         gameInfo.round = StrSave("-");
4072         gameInfo.white = StrSave(white);
4073         gameInfo.black = StrSave(black);
4074         timeControl = basetime * 60 * 1000;
4075         timeControl_2 = 0;
4076         timeIncrement = increment * 1000;
4077         movesPerSession = 0;
4078         gameInfo.timeControl = TimeControlTagValue();
4079         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4080   if (appData.debugMode) {
4081     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4082     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4083     setbuf(debugFP, NULL);
4084   }
4085
4086         gameInfo.outOfBook = NULL;
4087
4088         /* Do we have the ratings? */
4089         if (strcmp(player1Name, white) == 0 &&
4090             strcmp(player2Name, black) == 0) {
4091             if (appData.debugMode)
4092               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4093                       player1Rating, player2Rating);
4094             gameInfo.whiteRating = player1Rating;
4095             gameInfo.blackRating = player2Rating;
4096         } else if (strcmp(player2Name, white) == 0 &&
4097                    strcmp(player1Name, black) == 0) {
4098             if (appData.debugMode)
4099               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4100                       player2Rating, player1Rating);
4101             gameInfo.whiteRating = player2Rating;
4102             gameInfo.blackRating = player1Rating;
4103         }
4104         player1Name[0] = player2Name[0] = NULLCHAR;
4105
4106         /* Silence shouts if requested */
4107         if (appData.quietPlay &&
4108             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4109             SendToICS(ics_prefix);
4110             SendToICS("set shout 0\n");
4111         }
4112     }
4113
4114     /* Deal with midgame name changes */
4115     if (!newGame) {
4116         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4117             if (gameInfo.white) free(gameInfo.white);
4118             gameInfo.white = StrSave(white);
4119         }
4120         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4121             if (gameInfo.black) free(gameInfo.black);
4122             gameInfo.black = StrSave(black);
4123         }
4124     }
4125
4126     /* Throw away game result if anything actually changes in examine mode */
4127     if (gameMode == IcsExamining && !newGame) {
4128         gameInfo.result = GameUnfinished;
4129         if (gameInfo.resultDetails != NULL) {
4130             free(gameInfo.resultDetails);
4131             gameInfo.resultDetails = NULL;
4132         }
4133     }
4134
4135     /* In pausing && IcsExamining mode, we ignore boards coming
4136        in if they are in a different variation than we are. */
4137     if (pauseExamInvalid) return;
4138     if (pausing && gameMode == IcsExamining) {
4139         if (moveNum <= pauseExamForwardMostMove) {
4140             pauseExamInvalid = TRUE;
4141             forwardMostMove = pauseExamForwardMostMove;
4142             return;
4143         }
4144     }
4145
4146   if (appData.debugMode) {
4147     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4148   }
4149     /* Parse the board */
4150     for (k = 0; k < ranks; k++) {
4151       for (j = 0; j < files; j++)
4152         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4153       if(gameInfo.holdingsWidth > 1) {
4154            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4155            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4156       }
4157     }
4158     CopyBoard(boards[moveNum], board);
4159     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4160     if (moveNum == 0) {
4161         startedFromSetupPosition =
4162           !CompareBoards(board, initialPosition);
4163         if(startedFromSetupPosition)
4164             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4165     }
4166
4167     /* [HGM] Set castling rights. Take the outermost Rooks,
4168        to make it also work for FRC opening positions. Note that board12
4169        is really defective for later FRC positions, as it has no way to
4170        indicate which Rook can castle if they are on the same side of King.
4171        For the initial position we grant rights to the outermost Rooks,
4172        and remember thos rights, and we then copy them on positions
4173        later in an FRC game. This means WB might not recognize castlings with
4174        Rooks that have moved back to their original position as illegal,
4175        but in ICS mode that is not its job anyway.
4176     */
4177     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4178     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4179
4180         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4181             if(board[0][i] == WhiteRook) j = i;
4182         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4183         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4184             if(board[0][i] == WhiteRook) j = i;
4185         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4186         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4187             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4188         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4189         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4190             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4191         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4192
4193         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4194         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4195             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4196         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4197             if(board[BOARD_HEIGHT-1][k] == bKing)
4198                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4199         if(gameInfo.variant == VariantTwoKings) {
4200             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4201             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4202             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4203         }
4204     } else { int r;
4205         r = boards[moveNum][CASTLING][0] = initialRights[0];
4206         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4207         r = boards[moveNum][CASTLING][1] = initialRights[1];
4208         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4209         r = boards[moveNum][CASTLING][3] = initialRights[3];
4210         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4211         r = boards[moveNum][CASTLING][4] = initialRights[4];
4212         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4213         /* wildcastle kludge: always assume King has rights */
4214         r = boards[moveNum][CASTLING][2] = initialRights[2];
4215         r = boards[moveNum][CASTLING][5] = initialRights[5];
4216     }
4217     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4218     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4219
4220
4221     if (ics_getting_history == H_GOT_REQ_HEADER ||
4222         ics_getting_history == H_GOT_UNREQ_HEADER) {
4223         /* This was an initial position from a move list, not
4224            the current position */
4225         return;
4226     }
4227
4228     /* Update currentMove and known move number limits */
4229     newMove = newGame || moveNum > forwardMostMove;
4230
4231     if (newGame) {
4232         forwardMostMove = backwardMostMove = currentMove = moveNum;
4233         if (gameMode == IcsExamining && moveNum == 0) {
4234           /* Workaround for ICS limitation: we are not told the wild
4235              type when starting to examine a game.  But if we ask for
4236              the move list, the move list header will tell us */
4237             ics_getting_history = H_REQUESTED;
4238             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4239             SendToICS(str);
4240         }
4241     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4242                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4243 #if ZIPPY
4244         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4245         /* [HGM] applied this also to an engine that is silently watching        */
4246         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4247             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4248             gameInfo.variant == currentlyInitializedVariant) {
4249           takeback = forwardMostMove - moveNum;
4250           for (i = 0; i < takeback; i++) {
4251             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4252             SendToProgram("undo\n", &first);
4253           }
4254         }
4255 #endif
4256
4257         forwardMostMove = moveNum;
4258         if (!pausing || currentMove > forwardMostMove)
4259           currentMove = forwardMostMove;
4260     } else {
4261         /* New part of history that is not contiguous with old part */
4262         if (pausing && gameMode == IcsExamining) {
4263             pauseExamInvalid = TRUE;
4264             forwardMostMove = pauseExamForwardMostMove;
4265             return;
4266         }
4267         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4268 #if ZIPPY
4269             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4270                 // [HGM] when we will receive the move list we now request, it will be
4271                 // fed to the engine from the first move on. So if the engine is not
4272                 // in the initial position now, bring it there.
4273                 InitChessProgram(&first, 0);
4274             }
4275 #endif
4276             ics_getting_history = H_REQUESTED;
4277             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4278             SendToICS(str);
4279         }
4280         forwardMostMove = backwardMostMove = currentMove = moveNum;
4281     }
4282
4283     /* Update the clocks */
4284     if (strchr(elapsed_time, '.')) {
4285       /* Time is in ms */
4286       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4287       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4288     } else {
4289       /* Time is in seconds */
4290       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4291       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4292     }
4293
4294
4295 #if ZIPPY
4296     if (appData.zippyPlay && newGame &&
4297         gameMode != IcsObserving && gameMode != IcsIdle &&
4298         gameMode != IcsExamining)
4299       ZippyFirstBoard(moveNum, basetime, increment);
4300 #endif
4301
4302     /* Put the move on the move list, first converting
4303        to canonical algebraic form. */
4304     if (moveNum > 0) {
4305   if (appData.debugMode) {
4306     if (appData.debugMode) { int f = forwardMostMove;
4307         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4308                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4309                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4310     }
4311     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4312     fprintf(debugFP, "moveNum = %d\n", moveNum);
4313     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4314     setbuf(debugFP, NULL);
4315   }
4316         if (moveNum <= backwardMostMove) {
4317             /* We don't know what the board looked like before
4318                this move.  Punt. */
4319           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4320             strcat(parseList[moveNum - 1], " ");
4321             strcat(parseList[moveNum - 1], elapsed_time);
4322             moveList[moveNum - 1][0] = NULLCHAR;
4323         } else if (strcmp(move_str, "none") == 0) {
4324             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4325             /* Again, we don't know what the board looked like;
4326                this is really the start of the game. */
4327             parseList[moveNum - 1][0] = NULLCHAR;
4328             moveList[moveNum - 1][0] = NULLCHAR;
4329             backwardMostMove = moveNum;
4330             startedFromSetupPosition = TRUE;
4331             fromX = fromY = toX = toY = -1;
4332         } else {
4333           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4334           //                 So we parse the long-algebraic move string in stead of the SAN move
4335           int valid; char buf[MSG_SIZ], *prom;
4336
4337           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4338                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4339           // str looks something like "Q/a1-a2"; kill the slash
4340           if(str[1] == '/')
4341             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4342           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4343           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4344                 strcat(buf, prom); // long move lacks promo specification!
4345           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4346                 if(appData.debugMode)
4347                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4348                 safeStrCpy(move_str, buf, MSG_SIZ);
4349           }
4350           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4351                                 &fromX, &fromY, &toX, &toY, &promoChar)
4352                || ParseOneMove(buf, moveNum - 1, &moveType,
4353                                 &fromX, &fromY, &toX, &toY, &promoChar);
4354           // end of long SAN patch
4355           if (valid) {
4356             (void) CoordsToAlgebraic(boards[moveNum - 1],
4357                                      PosFlags(moveNum - 1),
4358                                      fromY, fromX, toY, toX, promoChar,
4359                                      parseList[moveNum-1]);
4360             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4361               case MT_NONE:
4362               case MT_STALEMATE:
4363               default:
4364                 break;
4365               case MT_CHECK:
4366                 if(gameInfo.variant != VariantShogi)
4367                     strcat(parseList[moveNum - 1], "+");
4368                 break;
4369               case MT_CHECKMATE:
4370               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4371                 strcat(parseList[moveNum - 1], "#");
4372                 break;
4373             }
4374             strcat(parseList[moveNum - 1], " ");
4375             strcat(parseList[moveNum - 1], elapsed_time);
4376             /* currentMoveString is set as a side-effect of ParseOneMove */
4377             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4378             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4379             strcat(moveList[moveNum - 1], "\n");
4380
4381             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4382                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4383               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4384                 ChessSquare old, new = boards[moveNum][k][j];
4385                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4386                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4387                   if(old == new) continue;
4388                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4389                   else if(new == WhiteWazir || new == BlackWazir) {
4390                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4391                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4392                       else boards[moveNum][k][j] = old; // preserve type of Gold
4393                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4394                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4395               }
4396           } else {
4397             /* Move from ICS was illegal!?  Punt. */
4398             if (appData.debugMode) {
4399               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4400               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4401             }
4402             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4403             strcat(parseList[moveNum - 1], " ");
4404             strcat(parseList[moveNum - 1], elapsed_time);
4405             moveList[moveNum - 1][0] = NULLCHAR;
4406             fromX = fromY = toX = toY = -1;
4407           }
4408         }
4409   if (appData.debugMode) {
4410     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4411     setbuf(debugFP, NULL);
4412   }
4413
4414 #if ZIPPY
4415         /* Send move to chess program (BEFORE animating it). */
4416         if (appData.zippyPlay && !newGame && newMove &&
4417            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4418
4419             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4420                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4421                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4422                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4423                             move_str);
4424                     DisplayError(str, 0);
4425                 } else {
4426                     if (first.sendTime) {
4427                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4428                     }
4429                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4430                     if (firstMove && !bookHit) {
4431                         firstMove = FALSE;
4432                         if (first.useColors) {
4433                           SendToProgram(gameMode == IcsPlayingWhite ?
4434                                         "white\ngo\n" :
4435                                         "black\ngo\n", &first);
4436                         } else {
4437                           SendToProgram("go\n", &first);
4438                         }
4439                         first.maybeThinking = TRUE;
4440                     }
4441                 }
4442             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4443               if (moveList[moveNum - 1][0] == NULLCHAR) {
4444                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4445                 DisplayError(str, 0);
4446               } else {
4447                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4448                 SendMoveToProgram(moveNum - 1, &first);
4449               }
4450             }
4451         }
4452 #endif
4453     }
4454
4455     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4456         /* If move comes from a remote source, animate it.  If it
4457            isn't remote, it will have already been animated. */
4458         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4459             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4460         }
4461         if (!pausing && appData.highlightLastMove) {
4462             SetHighlights(fromX, fromY, toX, toY);
4463         }
4464     }
4465
4466     /* Start the clocks */
4467     whiteFlag = blackFlag = FALSE;
4468     appData.clockMode = !(basetime == 0 && increment == 0);
4469     if (ticking == 0) {
4470       ics_clock_paused = TRUE;
4471       StopClocks();
4472     } else if (ticking == 1) {
4473       ics_clock_paused = FALSE;
4474     }
4475     if (gameMode == IcsIdle ||
4476         relation == RELATION_OBSERVING_STATIC ||
4477         relation == RELATION_EXAMINING ||
4478         ics_clock_paused)
4479       DisplayBothClocks();
4480     else
4481       StartClocks();
4482
4483     /* Display opponents and material strengths */
4484     if (gameInfo.variant != VariantBughouse &&
4485         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4486         if (tinyLayout || smallLayout) {
4487             if(gameInfo.variant == VariantNormal)
4488               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4489                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4490                     basetime, increment);
4491             else
4492               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4493                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4494                     basetime, increment, (int) gameInfo.variant);
4495         } else {
4496             if(gameInfo.variant == VariantNormal)
4497               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4498                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4499                     basetime, increment);
4500             else
4501               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4502                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4503                     basetime, increment, VariantName(gameInfo.variant));
4504         }
4505         DisplayTitle(str);
4506   if (appData.debugMode) {
4507     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4508   }
4509     }
4510
4511
4512     /* Display the board */
4513     if (!pausing && !appData.noGUI) {
4514
4515       if (appData.premove)
4516           if (!gotPremove ||
4517              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4518              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4519               ClearPremoveHighlights();
4520
4521       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4522         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4523       DrawPosition(j, boards[currentMove]);
4524
4525       DisplayMove(moveNum - 1);
4526       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4527             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4528               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4529         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4530       }
4531     }
4532
4533     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4534 #if ZIPPY
4535     if(bookHit) { // [HGM] book: simulate book reply
4536         static char bookMove[MSG_SIZ]; // a bit generous?
4537
4538         programStats.nodes = programStats.depth = programStats.time =
4539         programStats.score = programStats.got_only_move = 0;
4540         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4541
4542         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4543         strcat(bookMove, bookHit);
4544         HandleMachineMove(bookMove, &first);
4545     }
4546 #endif
4547 }
4548
4549 void
4550 GetMoveListEvent()
4551 {
4552     char buf[MSG_SIZ];
4553     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4554         ics_getting_history = H_REQUESTED;
4555         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4556         SendToICS(buf);
4557     }
4558 }
4559
4560 void
4561 AnalysisPeriodicEvent(force)
4562      int force;
4563 {
4564     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4565          && !force) || !appData.periodicUpdates)
4566       return;
4567
4568     /* Send . command to Crafty to collect stats */
4569     SendToProgram(".\n", &first);
4570
4571     /* Don't send another until we get a response (this makes
4572        us stop sending to old Crafty's which don't understand
4573        the "." command (sending illegal cmds resets node count & time,
4574        which looks bad)) */
4575     programStats.ok_to_send = 0;
4576 }
4577
4578 void ics_update_width(new_width)
4579         int new_width;
4580 {
4581         ics_printf("set width %d\n", new_width);
4582 }
4583
4584 void
4585 SendMoveToProgram(moveNum, cps)
4586      int moveNum;
4587      ChessProgramState *cps;
4588 {
4589     char buf[MSG_SIZ];
4590
4591     if (cps->useUsermove) {
4592       SendToProgram("usermove ", cps);
4593     }
4594     if (cps->useSAN) {
4595       char *space;
4596       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4597         int len = space - parseList[moveNum];
4598         memcpy(buf, parseList[moveNum], len);
4599         buf[len++] = '\n';
4600         buf[len] = NULLCHAR;
4601       } else {
4602         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4603       }
4604       SendToProgram(buf, cps);
4605     } else {
4606       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4607         AlphaRank(moveList[moveNum], 4);
4608         SendToProgram(moveList[moveNum], cps);
4609         AlphaRank(moveList[moveNum], 4); // and back
4610       } else
4611       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4612        * the engine. It would be nice to have a better way to identify castle
4613        * moves here. */
4614       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4615                                                                          && cps->useOOCastle) {
4616         int fromX = moveList[moveNum][0] - AAA;
4617         int fromY = moveList[moveNum][1] - ONE;
4618         int toX = moveList[moveNum][2] - AAA;
4619         int toY = moveList[moveNum][3] - ONE;
4620         if((boards[moveNum][fromY][fromX] == WhiteKing
4621             && boards[moveNum][toY][toX] == WhiteRook)
4622            || (boards[moveNum][fromY][fromX] == BlackKing
4623                && boards[moveNum][toY][toX] == BlackRook)) {
4624           if(toX > fromX) SendToProgram("O-O\n", cps);
4625           else SendToProgram("O-O-O\n", cps);
4626         }
4627         else SendToProgram(moveList[moveNum], cps);
4628       }
4629       else SendToProgram(moveList[moveNum], cps);
4630       /* End of additions by Tord */
4631     }
4632
4633     /* [HGM] setting up the opening has brought engine in force mode! */
4634     /*       Send 'go' if we are in a mode where machine should play. */
4635     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4636         (gameMode == TwoMachinesPlay   ||
4637 #if ZIPPY
4638          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4639 #endif
4640          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4641         SendToProgram("go\n", cps);
4642   if (appData.debugMode) {
4643     fprintf(debugFP, "(extra)\n");
4644   }
4645     }
4646     setboardSpoiledMachineBlack = 0;
4647 }
4648
4649 void
4650 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4651      ChessMove moveType;
4652      int fromX, fromY, toX, toY;
4653      char promoChar;
4654 {
4655     char user_move[MSG_SIZ];
4656
4657     switch (moveType) {
4658       default:
4659         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4660                 (int)moveType, fromX, fromY, toX, toY);
4661         DisplayError(user_move + strlen("say "), 0);
4662         break;
4663       case WhiteKingSideCastle:
4664       case BlackKingSideCastle:
4665       case WhiteQueenSideCastleWild:
4666       case BlackQueenSideCastleWild:
4667       /* PUSH Fabien */
4668       case WhiteHSideCastleFR:
4669       case BlackHSideCastleFR:
4670       /* POP Fabien */
4671         snprintf(user_move, MSG_SIZ, "o-o\n");
4672         break;
4673       case WhiteQueenSideCastle:
4674       case BlackQueenSideCastle:
4675       case WhiteKingSideCastleWild:
4676       case BlackKingSideCastleWild:
4677       /* PUSH Fabien */
4678       case WhiteASideCastleFR:
4679       case BlackASideCastleFR:
4680       /* POP Fabien */
4681         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4682         break;
4683       case WhiteNonPromotion:
4684       case BlackNonPromotion:
4685         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4686         break;
4687       case WhitePromotion:
4688       case BlackPromotion:
4689         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4690           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4691                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4692                 PieceToChar(WhiteFerz));
4693         else if(gameInfo.variant == VariantGreat)
4694           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4695                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4696                 PieceToChar(WhiteMan));
4697         else
4698           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4699                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4700                 promoChar);
4701         break;
4702       case WhiteDrop:
4703       case BlackDrop:
4704       drop:
4705         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4706                  ToUpper(PieceToChar((ChessSquare) fromX)),
4707                  AAA + toX, ONE + toY);
4708         break;
4709       case IllegalMove:  /* could be a variant we don't quite understand */
4710         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4711       case NormalMove:
4712       case WhiteCapturesEnPassant:
4713       case BlackCapturesEnPassant:
4714         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4715                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4716         break;
4717     }
4718     SendToICS(user_move);
4719     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4720         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4721 }
4722
4723 void
4724 UploadGameEvent()
4725 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4726     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4727     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4728     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4729         DisplayError("You cannot do this while you are playing or observing", 0);
4730         return;
4731     }
4732     if(gameMode != IcsExamining) { // is this ever not the case?
4733         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4734
4735         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4736           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4737         } else { // on FICS we must first go to general examine mode
4738           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4739         }
4740         if(gameInfo.variant != VariantNormal) {
4741             // try figure out wild number, as xboard names are not always valid on ICS
4742             for(i=1; i<=36; i++) {
4743               snprintf(buf, MSG_SIZ, "wild/%d", i);
4744                 if(StringToVariant(buf) == gameInfo.variant) break;
4745             }
4746             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4747             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4748             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4749         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4750         SendToICS(ics_prefix);
4751         SendToICS(buf);
4752         if(startedFromSetupPosition || backwardMostMove != 0) {
4753           fen = PositionToFEN(backwardMostMove, NULL);
4754           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4755             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4756             SendToICS(buf);
4757           } else { // FICS: everything has to set by separate bsetup commands
4758             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4759             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4760             SendToICS(buf);
4761             if(!WhiteOnMove(backwardMostMove)) {
4762                 SendToICS("bsetup tomove black\n");
4763             }
4764             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4765             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4766             SendToICS(buf);
4767             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4768             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4769             SendToICS(buf);
4770             i = boards[backwardMostMove][EP_STATUS];
4771             if(i >= 0) { // set e.p.
4772               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4773                 SendToICS(buf);
4774             }
4775             bsetup++;
4776           }
4777         }
4778       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4779             SendToICS("bsetup done\n"); // switch to normal examining.
4780     }
4781     for(i = backwardMostMove; i<last; i++) {
4782         char buf[20];
4783         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4784         SendToICS(buf);
4785     }
4786     SendToICS(ics_prefix);
4787     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4788 }
4789
4790 void
4791 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4792      int rf, ff, rt, ft;
4793      char promoChar;
4794      char move[7];
4795 {
4796     if (rf == DROP_RANK) {
4797       sprintf(move, "%c@%c%c\n",
4798                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4799     } else {
4800         if (promoChar == 'x' || promoChar == NULLCHAR) {
4801           sprintf(move, "%c%c%c%c\n",
4802                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4803         } else {
4804             sprintf(move, "%c%c%c%c%c\n",
4805                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4806         }
4807     }
4808 }
4809
4810 void
4811 ProcessICSInitScript(f)
4812      FILE *f;
4813 {
4814     char buf[MSG_SIZ];
4815
4816     while (fgets(buf, MSG_SIZ, f)) {
4817         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4818     }
4819
4820     fclose(f);
4821 }
4822
4823
4824 static int lastX, lastY, selectFlag, dragging;
4825
4826 void
4827 Sweep(int step)
4828 {
4829     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4830     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4831     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4832     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4833     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4834     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4835     do {
4836         promoSweep -= step;
4837         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4838         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4839         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4840         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4841         if(!step) step = 1;
4842     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4843             appData.testLegality && (promoSweep == king ||
4844             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4845     ChangeDragPiece(promoSweep);
4846 }
4847
4848 int PromoScroll(int x, int y)
4849 {
4850   int step = 0;
4851
4852   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4853   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4854   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4855   if(!step) return FALSE;
4856   lastX = x; lastY = y;
4857   if((promoSweep < BlackPawn) == flipView) step = -step;
4858   if(step > 0) selectFlag = 1;
4859   if(!selectFlag) Sweep(step);
4860   return FALSE;
4861 }
4862
4863 void
4864 NextPiece(int step)
4865 {
4866     ChessSquare piece = boards[currentMove][toY][toX];
4867     do {
4868         pieceSweep -= step;
4869         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4870         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4871         if(!step) step = -1;
4872     } while(PieceToChar(pieceSweep) == '.');
4873     boards[currentMove][toY][toX] = pieceSweep;
4874     DrawPosition(FALSE, boards[currentMove]);
4875     boards[currentMove][toY][toX] = piece;
4876 }
4877 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4878 void
4879 AlphaRank(char *move, int n)
4880 {
4881 //    char *p = move, c; int x, y;
4882
4883     if (appData.debugMode) {
4884         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4885     }
4886
4887     if(move[1]=='*' &&
4888        move[2]>='0' && move[2]<='9' &&
4889        move[3]>='a' && move[3]<='x'    ) {
4890         move[1] = '@';
4891         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4892         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4893     } else
4894     if(move[0]>='0' && move[0]<='9' &&
4895        move[1]>='a' && move[1]<='x' &&
4896        move[2]>='0' && move[2]<='9' &&
4897        move[3]>='a' && move[3]<='x'    ) {
4898         /* input move, Shogi -> normal */
4899         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4900         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4901         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4902         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4903     } else
4904     if(move[1]=='@' &&
4905        move[3]>='0' && move[3]<='9' &&
4906        move[2]>='a' && move[2]<='x'    ) {
4907         move[1] = '*';
4908         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4909         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4910     } else
4911     if(
4912        move[0]>='a' && move[0]<='x' &&
4913        move[3]>='0' && move[3]<='9' &&
4914        move[2]>='a' && move[2]<='x'    ) {
4915          /* output move, normal -> Shogi */
4916         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4917         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4918         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4919         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4920         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4921     }
4922     if (appData.debugMode) {
4923         fprintf(debugFP, "   out = '%s'\n", move);
4924     }
4925 }
4926
4927 char yy_textstr[8000];
4928
4929 /* Parser for moves from gnuchess, ICS, or user typein box */
4930 Boolean
4931 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4932      char *move;
4933      int moveNum;
4934      ChessMove *moveType;
4935      int *fromX, *fromY, *toX, *toY;
4936      char *promoChar;
4937 {
4938     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4939
4940     switch (*moveType) {
4941       case WhitePromotion:
4942       case BlackPromotion:
4943       case WhiteNonPromotion:
4944       case BlackNonPromotion:
4945       case NormalMove:
4946       case WhiteCapturesEnPassant:
4947       case BlackCapturesEnPassant:
4948       case WhiteKingSideCastle:
4949       case WhiteQueenSideCastle:
4950       case BlackKingSideCastle:
4951       case BlackQueenSideCastle:
4952       case WhiteKingSideCastleWild:
4953       case WhiteQueenSideCastleWild:
4954       case BlackKingSideCastleWild:
4955       case BlackQueenSideCastleWild:
4956       /* Code added by Tord: */
4957       case WhiteHSideCastleFR:
4958       case WhiteASideCastleFR:
4959       case BlackHSideCastleFR:
4960       case BlackASideCastleFR:
4961       /* End of code added by Tord */
4962       case IllegalMove:         /* bug or odd chess variant */
4963         *fromX = currentMoveString[0] - AAA;
4964         *fromY = currentMoveString[1] - ONE;
4965         *toX = currentMoveString[2] - AAA;
4966         *toY = currentMoveString[3] - ONE;
4967         *promoChar = currentMoveString[4];
4968         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4969             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4970     if (appData.debugMode) {
4971         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4972     }
4973             *fromX = *fromY = *toX = *toY = 0;
4974             return FALSE;
4975         }
4976         if (appData.testLegality) {
4977           return (*moveType != IllegalMove);
4978         } else {
4979           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4980                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4981         }
4982
4983       case WhiteDrop:
4984       case BlackDrop:
4985         *fromX = *moveType == WhiteDrop ?
4986           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4987           (int) CharToPiece(ToLower(currentMoveString[0]));
4988         *fromY = DROP_RANK;
4989         *toX = currentMoveString[2] - AAA;
4990         *toY = currentMoveString[3] - ONE;
4991         *promoChar = NULLCHAR;
4992         return TRUE;
4993
4994       case AmbiguousMove:
4995       case ImpossibleMove:
4996       case EndOfFile:
4997       case ElapsedTime:
4998       case Comment:
4999       case PGNTag:
5000       case NAG:
5001       case WhiteWins:
5002       case BlackWins:
5003       case GameIsDrawn:
5004       default:
5005     if (appData.debugMode) {
5006         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5007     }
5008         /* bug? */
5009         *fromX = *fromY = *toX = *toY = 0;
5010         *promoChar = NULLCHAR;
5011         return FALSE;
5012     }
5013 }
5014
5015
5016 void
5017 ParsePV(char *pv, Boolean storeComments)
5018 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5019   int fromX, fromY, toX, toY; char promoChar;
5020   ChessMove moveType;
5021   Boolean valid;
5022   int nr = 0;
5023
5024   endPV = forwardMostMove;
5025   do {
5026     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5027     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5028     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5029 if(appData.debugMode){
5030 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);
5031 }
5032     if(!valid && nr == 0 &&
5033        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5034         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5035         // Hande case where played move is different from leading PV move
5036         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5037         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5038         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5039         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5040           endPV += 2; // if position different, keep this
5041           moveList[endPV-1][0] = fromX + AAA;
5042           moveList[endPV-1][1] = fromY + ONE;
5043           moveList[endPV-1][2] = toX + AAA;
5044           moveList[endPV-1][3] = toY + ONE;
5045           parseList[endPV-1][0] = NULLCHAR;
5046           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5047         }
5048       }
5049     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5050     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5051     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5052     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5053         valid++; // allow comments in PV
5054         continue;
5055     }
5056     nr++;
5057     if(endPV+1 > framePtr) break; // no space, truncate
5058     if(!valid) break;
5059     endPV++;
5060     CopyBoard(boards[endPV], boards[endPV-1]);
5061     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5062     moveList[endPV-1][0] = fromX + AAA;
5063     moveList[endPV-1][1] = fromY + ONE;
5064     moveList[endPV-1][2] = toX + AAA;
5065     moveList[endPV-1][3] = toY + ONE;
5066     moveList[endPV-1][4] = promoChar;
5067     moveList[endPV-1][5] = NULLCHAR;
5068     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5069     if(storeComments)
5070         CoordsToAlgebraic(boards[endPV - 1],
5071                              PosFlags(endPV - 1),
5072                              fromY, fromX, toY, toX, promoChar,
5073                              parseList[endPV - 1]);
5074     else
5075         parseList[endPV-1][0] = NULLCHAR;
5076   } while(valid);
5077   currentMove = endPV;
5078   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5079   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5080                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5081   DrawPosition(TRUE, boards[currentMove]);
5082 }
5083
5084 Boolean
5085 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5086 {
5087         int startPV;
5088         char *p;
5089
5090         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5091         lastX = x; lastY = y;
5092         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5093         startPV = index;
5094         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5095         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5096         index = startPV;
5097         do{ while(buf[index] && buf[index] != '\n') index++;
5098         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5099         buf[index] = 0;
5100         ParsePV(buf+startPV, FALSE);
5101         *start = startPV; *end = index-1;
5102         return TRUE;
5103 }
5104
5105 Boolean
5106 LoadPV(int x, int y)
5107 { // called on right mouse click to load PV
5108   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5109   lastX = x; lastY = y;
5110   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5111   return TRUE;
5112 }
5113
5114 void
5115 UnLoadPV()
5116 {
5117   if(endPV < 0) return;
5118   endPV = -1;
5119   currentMove = forwardMostMove;
5120   ClearPremoveHighlights();
5121   DrawPosition(TRUE, boards[currentMove]);
5122 }
5123
5124 void
5125 MovePV(int x, int y, int h)
5126 { // step through PV based on mouse coordinates (called on mouse move)
5127   int margin = h>>3, step = 0;
5128
5129   // we must somehow check if right button is still down (might be released off board!)
5130   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5131   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5132   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5133   if(!step) return;
5134   lastX = x; lastY = y;
5135
5136   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5137   if(endPV < 0) return;
5138   if(y < margin) step = 1; else
5139   if(y > h - margin) step = -1;
5140   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5141   currentMove += step;
5142   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5143   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5144                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5145   DrawPosition(FALSE, boards[currentMove]);
5146 }
5147
5148
5149 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5150 // All positions will have equal probability, but the current method will not provide a unique
5151 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5152 #define DARK 1
5153 #define LITE 2
5154 #define ANY 3
5155
5156 int squaresLeft[4];
5157 int piecesLeft[(int)BlackPawn];
5158 int seed, nrOfShuffles;
5159
5160 void GetPositionNumber()
5161 {       // sets global variable seed
5162         int i;
5163
5164         seed = appData.defaultFrcPosition;
5165         if(seed < 0) { // randomize based on time for negative FRC position numbers
5166                 for(i=0; i<50; i++) seed += random();
5167                 seed = random() ^ random() >> 8 ^ random() << 8;
5168                 if(seed<0) seed = -seed;
5169         }
5170 }
5171
5172 int put(Board board, int pieceType, int rank, int n, int shade)
5173 // put the piece on the (n-1)-th empty squares of the given shade
5174 {
5175         int i;
5176
5177         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5178                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5179                         board[rank][i] = (ChessSquare) pieceType;
5180                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5181                         squaresLeft[ANY]--;
5182                         piecesLeft[pieceType]--;
5183                         return i;
5184                 }
5185         }
5186         return -1;
5187 }
5188
5189
5190 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5191 // calculate where the next piece goes, (any empty square), and put it there
5192 {
5193         int i;
5194
5195         i = seed % squaresLeft[shade];
5196         nrOfShuffles *= squaresLeft[shade];
5197         seed /= squaresLeft[shade];
5198         put(board, pieceType, rank, i, shade);
5199 }
5200
5201 void AddTwoPieces(Board board, int pieceType, int rank)
5202 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5203 {
5204         int i, n=squaresLeft[ANY], j=n-1, k;
5205
5206         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5207         i = seed % k;  // pick one
5208         nrOfShuffles *= k;
5209         seed /= k;
5210         while(i >= j) i -= j--;
5211         j = n - 1 - j; i += j;
5212         put(board, pieceType, rank, j, ANY);
5213         put(board, pieceType, rank, i, ANY);
5214 }
5215
5216 void SetUpShuffle(Board board, int number)
5217 {
5218         int i, p, first=1;
5219
5220         GetPositionNumber(); nrOfShuffles = 1;
5221
5222         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5223         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5224         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5225
5226         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5227
5228         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5229             p = (int) board[0][i];
5230             if(p < (int) BlackPawn) piecesLeft[p] ++;
5231             board[0][i] = EmptySquare;
5232         }
5233
5234         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5235             // shuffles restricted to allow normal castling put KRR first
5236             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5237                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5238             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5239                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5240             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5241                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5242             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5243                 put(board, WhiteRook, 0, 0, ANY);
5244             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5245         }
5246
5247         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5248             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5249             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5250                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5251                 while(piecesLeft[p] >= 2) {
5252                     AddOnePiece(board, p, 0, LITE);
5253                     AddOnePiece(board, p, 0, DARK);
5254                 }
5255                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5256             }
5257
5258         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5259             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5260             // but we leave King and Rooks for last, to possibly obey FRC restriction
5261             if(p == (int)WhiteRook) continue;
5262             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5263             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5264         }
5265
5266         // now everything is placed, except perhaps King (Unicorn) and Rooks
5267
5268         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5269             // Last King gets castling rights
5270             while(piecesLeft[(int)WhiteUnicorn]) {
5271                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5272                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5273             }
5274
5275             while(piecesLeft[(int)WhiteKing]) {
5276                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5277                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5278             }
5279
5280
5281         } else {
5282             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5283             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5284         }
5285
5286         // Only Rooks can be left; simply place them all
5287         while(piecesLeft[(int)WhiteRook]) {
5288                 i = put(board, WhiteRook, 0, 0, ANY);
5289                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5290                         if(first) {
5291                                 first=0;
5292                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5293                         }
5294                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5295                 }
5296         }
5297         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5298             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5299         }
5300
5301         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5302 }
5303
5304 int SetCharTable( char *table, const char * map )
5305 /* [HGM] moved here from winboard.c because of its general usefulness */
5306 /*       Basically a safe strcpy that uses the last character as King */
5307 {
5308     int result = FALSE; int NrPieces;
5309
5310     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5311                     && NrPieces >= 12 && !(NrPieces&1)) {
5312         int i; /* [HGM] Accept even length from 12 to 34 */
5313
5314         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5315         for( i=0; i<NrPieces/2-1; i++ ) {
5316             table[i] = map[i];
5317             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5318         }
5319         table[(int) WhiteKing]  = map[NrPieces/2-1];
5320         table[(int) BlackKing]  = map[NrPieces-1];
5321
5322         result = TRUE;
5323     }
5324
5325     return result;
5326 }
5327
5328 void Prelude(Board board)
5329 {       // [HGM] superchess: random selection of exo-pieces
5330         int i, j, k; ChessSquare p;
5331         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5332
5333         GetPositionNumber(); // use FRC position number
5334
5335         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5336             SetCharTable(pieceToChar, appData.pieceToCharTable);
5337             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5338                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5339         }
5340
5341         j = seed%4;                 seed /= 4;
5342         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5343         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5344         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5345         j = seed%3 + (seed%3 >= j); seed /= 3;
5346         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5347         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5348         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5349         j = seed%3;                 seed /= 3;
5350         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5351         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5352         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5353         j = seed%2 + (seed%2 >= j); seed /= 2;
5354         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5355         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5356         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5357         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5358         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5359         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5360         put(board, exoPieces[0],    0, 0, ANY);
5361         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5362 }
5363
5364 void
5365 InitPosition(redraw)
5366      int redraw;
5367 {
5368     ChessSquare (* pieces)[BOARD_FILES];
5369     int i, j, pawnRow, overrule,
5370     oldx = gameInfo.boardWidth,
5371     oldy = gameInfo.boardHeight,
5372     oldh = gameInfo.holdingsWidth;
5373     static int oldv;
5374
5375     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5376
5377     /* [AS] Initialize pv info list [HGM] and game status */
5378     {
5379         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5380             pvInfoList[i].depth = 0;
5381             boards[i][EP_STATUS] = EP_NONE;
5382             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5383         }
5384
5385         initialRulePlies = 0; /* 50-move counter start */
5386
5387         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5388         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5389     }
5390
5391
5392     /* [HGM] logic here is completely changed. In stead of full positions */
5393     /* the initialized data only consist of the two backranks. The switch */
5394     /* selects which one we will use, which is than copied to the Board   */
5395     /* initialPosition, which for the rest is initialized by Pawns and    */
5396     /* empty squares. This initial position is then copied to boards[0],  */
5397     /* possibly after shuffling, so that it remains available.            */
5398
5399     gameInfo.holdingsWidth = 0; /* default board sizes */
5400     gameInfo.boardWidth    = 8;
5401     gameInfo.boardHeight   = 8;
5402     gameInfo.holdingsSize  = 0;
5403     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5404     for(i=0; i<BOARD_FILES-2; i++)
5405       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5406     initialPosition[EP_STATUS] = EP_NONE;
5407     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5408     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5409          SetCharTable(pieceNickName, appData.pieceNickNames);
5410     else SetCharTable(pieceNickName, "............");
5411     pieces = FIDEArray;
5412
5413     switch (gameInfo.variant) {
5414     case VariantFischeRandom:
5415       shuffleOpenings = TRUE;
5416     default:
5417       break;
5418     case VariantShatranj:
5419       pieces = ShatranjArray;
5420       nrCastlingRights = 0;
5421       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5422       break;
5423     case VariantMakruk:
5424       pieces = makrukArray;
5425       nrCastlingRights = 0;
5426       startedFromSetupPosition = TRUE;
5427       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5428       break;
5429     case VariantTwoKings:
5430       pieces = twoKingsArray;
5431       break;
5432     case VariantCapaRandom:
5433       shuffleOpenings = TRUE;
5434     case VariantCapablanca:
5435       pieces = CapablancaArray;
5436       gameInfo.boardWidth = 10;
5437       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5438       break;
5439     case VariantGothic:
5440       pieces = GothicArray;
5441       gameInfo.boardWidth = 10;
5442       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5443       break;
5444     case VariantSChess:
5445       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5446       gameInfo.holdingsSize = 7;
5447       break;
5448     case VariantJanus:
5449       pieces = JanusArray;
5450       gameInfo.boardWidth = 10;
5451       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5452       nrCastlingRights = 6;
5453         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5454         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5455         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5456         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5457         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5458         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5459       break;
5460     case VariantFalcon:
5461       pieces = FalconArray;
5462       gameInfo.boardWidth = 10;
5463       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5464       break;
5465     case VariantXiangqi:
5466       pieces = XiangqiArray;
5467       gameInfo.boardWidth  = 9;
5468       gameInfo.boardHeight = 10;
5469       nrCastlingRights = 0;
5470       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5471       break;
5472     case VariantShogi:
5473       pieces = ShogiArray;
5474       gameInfo.boardWidth  = 9;
5475       gameInfo.boardHeight = 9;
5476       gameInfo.holdingsSize = 7;
5477       nrCastlingRights = 0;
5478       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5479       break;
5480     case VariantCourier:
5481       pieces = CourierArray;
5482       gameInfo.boardWidth  = 12;
5483       nrCastlingRights = 0;
5484       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5485       break;
5486     case VariantKnightmate:
5487       pieces = KnightmateArray;
5488       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5489       break;
5490     case VariantSpartan:
5491       pieces = SpartanArray;
5492       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5493       break;
5494     case VariantFairy:
5495       pieces = fairyArray;
5496       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5497       break;
5498     case VariantGreat:
5499       pieces = GreatArray;
5500       gameInfo.boardWidth = 10;
5501       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5502       gameInfo.holdingsSize = 8;
5503       break;
5504     case VariantSuper:
5505       pieces = FIDEArray;
5506       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5507       gameInfo.holdingsSize = 8;
5508       startedFromSetupPosition = TRUE;
5509       break;
5510     case VariantCrazyhouse:
5511     case VariantBughouse:
5512       pieces = FIDEArray;
5513       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5514       gameInfo.holdingsSize = 5;
5515       break;
5516     case VariantWildCastle:
5517       pieces = FIDEArray;
5518       /* !!?shuffle with kings guaranteed to be on d or e file */
5519       shuffleOpenings = 1;
5520       break;
5521     case VariantNoCastle:
5522       pieces = FIDEArray;
5523       nrCastlingRights = 0;
5524       /* !!?unconstrained back-rank shuffle */
5525       shuffleOpenings = 1;
5526       break;
5527     }
5528
5529     overrule = 0;
5530     if(appData.NrFiles >= 0) {
5531         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5532         gameInfo.boardWidth = appData.NrFiles;
5533     }
5534     if(appData.NrRanks >= 0) {
5535         gameInfo.boardHeight = appData.NrRanks;
5536     }
5537     if(appData.holdingsSize >= 0) {
5538         i = appData.holdingsSize;
5539         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5540         gameInfo.holdingsSize = i;
5541     }
5542     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5543     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5544         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5545
5546     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5547     if(pawnRow < 1) pawnRow = 1;
5548     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5549
5550     /* User pieceToChar list overrules defaults */
5551     if(appData.pieceToCharTable != NULL)
5552         SetCharTable(pieceToChar, appData.pieceToCharTable);
5553
5554     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5555
5556         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5557             s = (ChessSquare) 0; /* account holding counts in guard band */
5558         for( i=0; i<BOARD_HEIGHT; i++ )
5559             initialPosition[i][j] = s;
5560
5561         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5562         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5563         initialPosition[pawnRow][j] = WhitePawn;
5564         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5565         if(gameInfo.variant == VariantXiangqi) {
5566             if(j&1) {
5567                 initialPosition[pawnRow][j] =
5568                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5569                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5570                    initialPosition[2][j] = WhiteCannon;
5571                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5572                 }
5573             }
5574         }
5575         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5576     }
5577     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5578
5579             j=BOARD_LEFT+1;
5580             initialPosition[1][j] = WhiteBishop;
5581             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5582             j=BOARD_RGHT-2;
5583             initialPosition[1][j] = WhiteRook;
5584             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5585     }
5586
5587     if( nrCastlingRights == -1) {
5588         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5589         /*       This sets default castling rights from none to normal corners   */
5590         /* Variants with other castling rights must set them themselves above    */
5591         nrCastlingRights = 6;
5592
5593         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5594         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5595         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5596         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5597         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5598         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5599      }
5600
5601      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5602      if(gameInfo.variant == VariantGreat) { // promotion commoners
5603         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5604         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5605         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5606         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5607      }
5608      if( gameInfo.variant == VariantSChess ) {
5609       initialPosition[1][0] = BlackMarshall;
5610       initialPosition[2][0] = BlackAngel;
5611       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5612       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5613       initialPosition[1][1] = initialPosition[2][1] = 
5614       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5615      }
5616   if (appData.debugMode) {
5617     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5618   }
5619     if(shuffleOpenings) {
5620         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5621         startedFromSetupPosition = TRUE;
5622     }
5623     if(startedFromPositionFile) {
5624       /* [HGM] loadPos: use PositionFile for every new game */
5625       CopyBoard(initialPosition, filePosition);
5626       for(i=0; i<nrCastlingRights; i++)
5627           initialRights[i] = filePosition[CASTLING][i];
5628       startedFromSetupPosition = TRUE;
5629     }
5630
5631     CopyBoard(boards[0], initialPosition);
5632
5633     if(oldx != gameInfo.boardWidth ||
5634        oldy != gameInfo.boardHeight ||
5635        oldv != gameInfo.variant ||
5636        oldh != gameInfo.holdingsWidth
5637                                          )
5638             InitDrawingSizes(-2 ,0);
5639
5640     oldv = gameInfo.variant;
5641     if (redraw)
5642       DrawPosition(TRUE, boards[currentMove]);
5643 }
5644
5645 void
5646 SendBoard(cps, moveNum)
5647      ChessProgramState *cps;
5648      int moveNum;
5649 {
5650     char message[MSG_SIZ];
5651
5652     if (cps->useSetboard) {
5653       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5654       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5655       SendToProgram(message, cps);
5656       free(fen);
5657
5658     } else {
5659       ChessSquare *bp;
5660       int i, j;
5661       /* Kludge to set black to move, avoiding the troublesome and now
5662        * deprecated "black" command.
5663        */
5664       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5665         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5666
5667       SendToProgram("edit\n", cps);
5668       SendToProgram("#\n", cps);
5669       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5670         bp = &boards[moveNum][i][BOARD_LEFT];
5671         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5672           if ((int) *bp < (int) BlackPawn) {
5673             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5674                     AAA + j, ONE + i);
5675             if(message[0] == '+' || message[0] == '~') {
5676               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5677                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5678                         AAA + j, ONE + i);
5679             }
5680             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5681                 message[1] = BOARD_RGHT   - 1 - j + '1';
5682                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5683             }
5684             SendToProgram(message, cps);
5685           }
5686         }
5687       }
5688
5689       SendToProgram("c\n", cps);
5690       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5691         bp = &boards[moveNum][i][BOARD_LEFT];
5692         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5693           if (((int) *bp != (int) EmptySquare)
5694               && ((int) *bp >= (int) BlackPawn)) {
5695             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5696                     AAA + j, ONE + i);
5697             if(message[0] == '+' || message[0] == '~') {
5698               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5699                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5700                         AAA + j, ONE + i);
5701             }
5702             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5703                 message[1] = BOARD_RGHT   - 1 - j + '1';
5704                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5705             }
5706             SendToProgram(message, cps);
5707           }
5708         }
5709       }
5710
5711       SendToProgram(".\n", cps);
5712     }
5713     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5714 }
5715
5716 ChessSquare
5717 DefaultPromoChoice(int white)
5718 {
5719     ChessSquare result;
5720     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5721         result = WhiteFerz; // no choice
5722     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5723         result= WhiteKing; // in Suicide Q is the last thing we want
5724     else if(gameInfo.variant == VariantSpartan)
5725         result = white ? WhiteQueen : WhiteAngel;
5726     else result = WhiteQueen;
5727     if(!white) result = WHITE_TO_BLACK result;
5728     return result;
5729 }
5730
5731 static int autoQueen; // [HGM] oneclick
5732
5733 int
5734 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5735 {
5736     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5737     /* [HGM] add Shogi promotions */
5738     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5739     ChessSquare piece;
5740     ChessMove moveType;
5741     Boolean premove;
5742
5743     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5744     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5745
5746     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5747       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5748         return FALSE;
5749
5750     piece = boards[currentMove][fromY][fromX];
5751     if(gameInfo.variant == VariantShogi) {
5752         promotionZoneSize = BOARD_HEIGHT/3;
5753         highestPromotingPiece = (int)WhiteFerz;
5754     } else if(gameInfo.variant == VariantMakruk) {
5755         promotionZoneSize = 3;
5756     }
5757
5758     // Treat Lance as Pawn when it is not representing Amazon
5759     if(gameInfo.variant != VariantSuper) {
5760         if(piece == WhiteLance) piece = WhitePawn; else
5761         if(piece == BlackLance) piece = BlackPawn;
5762     }
5763
5764     // next weed out all moves that do not touch the promotion zone at all
5765     if((int)piece >= BlackPawn) {
5766         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5767              return FALSE;
5768         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5769     } else {
5770         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5771            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5772     }
5773
5774     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5775
5776     // weed out mandatory Shogi promotions
5777     if(gameInfo.variant == VariantShogi) {
5778         if(piece >= BlackPawn) {
5779             if(toY == 0 && piece == BlackPawn ||
5780                toY == 0 && piece == BlackQueen ||
5781                toY <= 1 && piece == BlackKnight) {
5782                 *promoChoice = '+';
5783                 return FALSE;
5784             }
5785         } else {
5786             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5787                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5788                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5789                 *promoChoice = '+';
5790                 return FALSE;
5791             }
5792         }
5793     }
5794
5795     // weed out obviously illegal Pawn moves
5796     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5797         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5798         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5799         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5800         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5801         // note we are not allowed to test for valid (non-)capture, due to premove
5802     }
5803
5804     // we either have a choice what to promote to, or (in Shogi) whether to promote
5805     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5806         *promoChoice = PieceToChar(BlackFerz);  // no choice
5807         return FALSE;
5808     }
5809     // no sense asking what we must promote to if it is going to explode...
5810     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5811         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5812         return FALSE;
5813     }
5814     // give caller the default choice even if we will not make it
5815     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5816     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5817     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5818                            && gameInfo.variant != VariantShogi
5819                            && gameInfo.variant != VariantSuper) return FALSE;
5820     if(autoQueen) return FALSE; // predetermined
5821
5822     // suppress promotion popup on illegal moves that are not premoves
5823     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5824               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5825     if(appData.testLegality && !premove) {
5826         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5827                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5828         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5829             return FALSE;
5830     }
5831
5832     return TRUE;
5833 }
5834
5835 int
5836 InPalace(row, column)
5837      int row, column;
5838 {   /* [HGM] for Xiangqi */
5839     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5840          column < (BOARD_WIDTH + 4)/2 &&
5841          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5842     return FALSE;
5843 }
5844
5845 int
5846 PieceForSquare (x, y)
5847      int x;
5848      int y;
5849 {
5850   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5851      return -1;
5852   else
5853      return boards[currentMove][y][x];
5854 }
5855
5856 int
5857 OKToStartUserMove(x, y)
5858      int x, y;
5859 {
5860     ChessSquare from_piece;
5861     int white_piece;
5862
5863     if (matchMode) return FALSE;
5864     if (gameMode == EditPosition) return TRUE;
5865
5866     if (x >= 0 && y >= 0)
5867       from_piece = boards[currentMove][y][x];
5868     else
5869       from_piece = EmptySquare;
5870
5871     if (from_piece == EmptySquare) return FALSE;
5872
5873     white_piece = (int)from_piece >= (int)WhitePawn &&
5874       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5875
5876     switch (gameMode) {
5877       case PlayFromGameFile:
5878       case AnalyzeFile:
5879       case TwoMachinesPlay:
5880       case EndOfGame:
5881         return FALSE;
5882
5883       case IcsObserving:
5884       case IcsIdle:
5885         return FALSE;
5886
5887       case MachinePlaysWhite:
5888       case IcsPlayingBlack:
5889         if (appData.zippyPlay) return FALSE;
5890         if (white_piece) {
5891             DisplayMoveError(_("You are playing Black"));
5892             return FALSE;
5893         }
5894         break;
5895
5896       case MachinePlaysBlack:
5897       case IcsPlayingWhite:
5898         if (appData.zippyPlay) return FALSE;
5899         if (!white_piece) {
5900             DisplayMoveError(_("You are playing White"));
5901             return FALSE;
5902         }
5903         break;
5904
5905       case EditGame:
5906         if (!white_piece && WhiteOnMove(currentMove)) {
5907             DisplayMoveError(_("It is White's turn"));
5908             return FALSE;
5909         }
5910         if (white_piece && !WhiteOnMove(currentMove)) {
5911             DisplayMoveError(_("It is Black's turn"));
5912             return FALSE;
5913         }
5914         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5915             /* Editing correspondence game history */
5916             /* Could disallow this or prompt for confirmation */
5917             cmailOldMove = -1;
5918         }
5919         break;
5920
5921       case BeginningOfGame:
5922         if (appData.icsActive) return FALSE;
5923         if (!appData.noChessProgram) {
5924             if (!white_piece) {
5925                 DisplayMoveError(_("You are playing White"));
5926                 return FALSE;
5927             }
5928         }
5929         break;
5930
5931       case Training:
5932         if (!white_piece && WhiteOnMove(currentMove)) {
5933             DisplayMoveError(_("It is White's turn"));
5934             return FALSE;
5935         }
5936         if (white_piece && !WhiteOnMove(currentMove)) {
5937             DisplayMoveError(_("It is Black's turn"));
5938             return FALSE;
5939         }
5940         break;
5941
5942       default:
5943       case IcsExamining:
5944         break;
5945     }
5946     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5947         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5948         && gameMode != AnalyzeFile && gameMode != Training) {
5949         DisplayMoveError(_("Displayed position is not current"));
5950         return FALSE;
5951     }
5952     return TRUE;
5953 }
5954
5955 Boolean
5956 OnlyMove(int *x, int *y, Boolean captures) {
5957     DisambiguateClosure cl;
5958     if (appData.zippyPlay) return FALSE;
5959     switch(gameMode) {
5960       case MachinePlaysBlack:
5961       case IcsPlayingWhite:
5962       case BeginningOfGame:
5963         if(!WhiteOnMove(currentMove)) return FALSE;
5964         break;
5965       case MachinePlaysWhite:
5966       case IcsPlayingBlack:
5967         if(WhiteOnMove(currentMove)) return FALSE;
5968         break;
5969       case EditGame:
5970         break;
5971       default:
5972         return FALSE;
5973     }
5974     cl.pieceIn = EmptySquare;
5975     cl.rfIn = *y;
5976     cl.ffIn = *x;
5977     cl.rtIn = -1;
5978     cl.ftIn = -1;
5979     cl.promoCharIn = NULLCHAR;
5980     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5981     if( cl.kind == NormalMove ||
5982         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5983         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5984         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5985       fromX = cl.ff;
5986       fromY = cl.rf;
5987       *x = cl.ft;
5988       *y = cl.rt;
5989       return TRUE;
5990     }
5991     if(cl.kind != ImpossibleMove) return FALSE;
5992     cl.pieceIn = EmptySquare;
5993     cl.rfIn = -1;
5994     cl.ffIn = -1;
5995     cl.rtIn = *y;
5996     cl.ftIn = *x;
5997     cl.promoCharIn = NULLCHAR;
5998     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5999     if( cl.kind == NormalMove ||
6000         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6001         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6002         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6003       fromX = cl.ff;
6004       fromY = cl.rf;
6005       *x = cl.ft;
6006       *y = cl.rt;
6007       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6008       return TRUE;
6009     }
6010     return FALSE;
6011 }
6012
6013 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6014 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6015 int lastLoadGameUseList = FALSE;
6016 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6017 ChessMove lastLoadGameStart = EndOfFile;
6018
6019 void
6020 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6021      int fromX, fromY, toX, toY;
6022      int promoChar;
6023 {
6024     ChessMove moveType;
6025     ChessSquare pdown, pup;
6026
6027     /* Check if the user is playing in turn.  This is complicated because we
6028        let the user "pick up" a piece before it is his turn.  So the piece he
6029        tried to pick up may have been captured by the time he puts it down!
6030        Therefore we use the color the user is supposed to be playing in this
6031        test, not the color of the piece that is currently on the starting
6032        square---except in EditGame mode, where the user is playing both
6033        sides; fortunately there the capture race can't happen.  (It can
6034        now happen in IcsExamining mode, but that's just too bad.  The user
6035        will get a somewhat confusing message in that case.)
6036        */
6037
6038     switch (gameMode) {
6039       case PlayFromGameFile:
6040       case AnalyzeFile:
6041       case TwoMachinesPlay:
6042       case EndOfGame:
6043       case IcsObserving:
6044       case IcsIdle:
6045         /* We switched into a game mode where moves are not accepted,
6046            perhaps while the mouse button was down. */
6047         return;
6048
6049       case MachinePlaysWhite:
6050         /* User is moving for Black */
6051         if (WhiteOnMove(currentMove)) {
6052             DisplayMoveError(_("It is White's turn"));
6053             return;
6054         }
6055         break;
6056
6057       case MachinePlaysBlack:
6058         /* User is moving for White */
6059         if (!WhiteOnMove(currentMove)) {
6060             DisplayMoveError(_("It is Black's turn"));
6061             return;
6062         }
6063         break;
6064
6065       case EditGame:
6066       case IcsExamining:
6067       case BeginningOfGame:
6068       case AnalyzeMode:
6069       case Training:
6070         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6071         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6072             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6073             /* User is moving for Black */
6074             if (WhiteOnMove(currentMove)) {
6075                 DisplayMoveError(_("It is White's turn"));
6076                 return;
6077             }
6078         } else {
6079             /* User is moving for White */
6080             if (!WhiteOnMove(currentMove)) {
6081                 DisplayMoveError(_("It is Black's turn"));
6082                 return;
6083             }
6084         }
6085         break;
6086
6087       case IcsPlayingBlack:
6088         /* User is moving for Black */
6089         if (WhiteOnMove(currentMove)) {
6090             if (!appData.premove) {
6091                 DisplayMoveError(_("It is White's turn"));
6092             } else if (toX >= 0 && toY >= 0) {
6093                 premoveToX = toX;
6094                 premoveToY = toY;
6095                 premoveFromX = fromX;
6096                 premoveFromY = fromY;
6097                 premovePromoChar = promoChar;
6098                 gotPremove = 1;
6099                 if (appData.debugMode)
6100                     fprintf(debugFP, "Got premove: fromX %d,"
6101                             "fromY %d, toX %d, toY %d\n",
6102                             fromX, fromY, toX, toY);
6103             }
6104             return;
6105         }
6106         break;
6107
6108       case IcsPlayingWhite:
6109         /* User is moving for White */
6110         if (!WhiteOnMove(currentMove)) {
6111             if (!appData.premove) {
6112                 DisplayMoveError(_("It is Black's turn"));
6113             } else if (toX >= 0 && toY >= 0) {
6114                 premoveToX = toX;
6115                 premoveToY = toY;
6116                 premoveFromX = fromX;
6117                 premoveFromY = fromY;
6118                 premovePromoChar = promoChar;
6119                 gotPremove = 1;
6120                 if (appData.debugMode)
6121                     fprintf(debugFP, "Got premove: fromX %d,"
6122                             "fromY %d, toX %d, toY %d\n",
6123                             fromX, fromY, toX, toY);
6124             }
6125             return;
6126         }
6127         break;
6128
6129       default:
6130         break;
6131
6132       case EditPosition:
6133         /* EditPosition, empty square, or different color piece;
6134            click-click move is possible */
6135         if (toX == -2 || toY == -2) {
6136             boards[0][fromY][fromX] = EmptySquare;
6137             DrawPosition(FALSE, boards[currentMove]);
6138             return;
6139         } else if (toX >= 0 && toY >= 0) {
6140             boards[0][toY][toX] = boards[0][fromY][fromX];
6141             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6142                 if(boards[0][fromY][0] != EmptySquare) {
6143                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6144                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6145                 }
6146             } else
6147             if(fromX == BOARD_RGHT+1) {
6148                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6149                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6150                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6151                 }
6152             } else
6153             boards[0][fromY][fromX] = EmptySquare;
6154             DrawPosition(FALSE, boards[currentMove]);
6155             return;
6156         }
6157         return;
6158     }
6159
6160     if(toX < 0 || toY < 0) return;
6161     pdown = boards[currentMove][fromY][fromX];
6162     pup = boards[currentMove][toY][toX];
6163
6164     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6165     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6166          if( pup != EmptySquare ) return;
6167          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6168            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6169                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6170            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6171            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6172            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6173            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6174          fromY = DROP_RANK;
6175     }
6176
6177     /* [HGM] always test for legality, to get promotion info */
6178     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6179                                          fromY, fromX, toY, toX, promoChar);
6180     /* [HGM] but possibly ignore an IllegalMove result */
6181     if (appData.testLegality) {
6182         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6183             DisplayMoveError(_("Illegal move"));
6184             return;
6185         }
6186     }
6187
6188     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6189 }
6190
6191 /* Common tail of UserMoveEvent and DropMenuEvent */
6192 int
6193 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6194      ChessMove moveType;
6195      int fromX, fromY, toX, toY;
6196      /*char*/int promoChar;
6197 {
6198     char *bookHit = 0;
6199
6200     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6201         // [HGM] superchess: suppress promotions to non-available piece
6202         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6203         if(WhiteOnMove(currentMove)) {
6204             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6205         } else {
6206             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6207         }
6208     }
6209
6210     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6211        move type in caller when we know the move is a legal promotion */
6212     if(moveType == NormalMove && promoChar)
6213         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6214
6215     /* [HGM] <popupFix> The following if has been moved here from
6216        UserMoveEvent(). Because it seemed to belong here (why not allow
6217        piece drops in training games?), and because it can only be
6218        performed after it is known to what we promote. */
6219     if (gameMode == Training) {
6220       /* compare the move played on the board to the next move in the
6221        * game. If they match, display the move and the opponent's response.
6222        * If they don't match, display an error message.
6223        */
6224       int saveAnimate;
6225       Board testBoard;
6226       CopyBoard(testBoard, boards[currentMove]);
6227       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6228
6229       if (CompareBoards(testBoard, boards[currentMove+1])) {
6230         ForwardInner(currentMove+1);
6231
6232         /* Autoplay the opponent's response.
6233          * if appData.animate was TRUE when Training mode was entered,
6234          * the response will be animated.
6235          */
6236         saveAnimate = appData.animate;
6237         appData.animate = animateTraining;
6238         ForwardInner(currentMove+1);
6239         appData.animate = saveAnimate;
6240
6241         /* check for the end of the game */
6242         if (currentMove >= forwardMostMove) {
6243           gameMode = PlayFromGameFile;
6244           ModeHighlight();
6245           SetTrainingModeOff();
6246           DisplayInformation(_("End of game"));
6247         }
6248       } else {
6249         DisplayError(_("Incorrect move"), 0);
6250       }
6251       return 1;
6252     }
6253
6254   /* Ok, now we know that the move is good, so we can kill
6255      the previous line in Analysis Mode */
6256   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6257                                 && currentMove < forwardMostMove) {
6258     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6259     else forwardMostMove = currentMove;
6260   }
6261
6262   /* If we need the chess program but it's dead, restart it */
6263   ResurrectChessProgram();
6264
6265   /* A user move restarts a paused game*/
6266   if (pausing)
6267     PauseEvent();
6268
6269   thinkOutput[0] = NULLCHAR;
6270
6271   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6272
6273   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6274     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6275     return 1;
6276   }
6277
6278   if (gameMode == BeginningOfGame) {
6279     if (appData.noChessProgram) {
6280       gameMode = EditGame;
6281       SetGameInfo();
6282     } else {
6283       char buf[MSG_SIZ];
6284       gameMode = MachinePlaysBlack;
6285       StartClocks();
6286       SetGameInfo();
6287       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6288       DisplayTitle(buf);
6289       if (first.sendName) {
6290         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6291         SendToProgram(buf, &first);
6292       }
6293       StartClocks();
6294     }
6295     ModeHighlight();
6296   }
6297
6298   /* Relay move to ICS or chess engine */
6299   if (appData.icsActive) {
6300     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6301         gameMode == IcsExamining) {
6302       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6303         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6304         SendToICS("draw ");
6305         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6306       }
6307       // also send plain move, in case ICS does not understand atomic claims
6308       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6309       ics_user_moved = 1;
6310     }
6311   } else {
6312     if (first.sendTime && (gameMode == BeginningOfGame ||
6313                            gameMode == MachinePlaysWhite ||
6314                            gameMode == MachinePlaysBlack)) {
6315       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6316     }
6317     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6318          // [HGM] book: if program might be playing, let it use book
6319         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6320         first.maybeThinking = TRUE;
6321     } else SendMoveToProgram(forwardMostMove-1, &first);
6322     if (currentMove == cmailOldMove + 1) {
6323       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6324     }
6325   }
6326
6327   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6328
6329   switch (gameMode) {
6330   case EditGame:
6331     if(appData.testLegality)
6332     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6333     case MT_NONE:
6334     case MT_CHECK:
6335       break;
6336     case MT_CHECKMATE:
6337     case MT_STAINMATE:
6338       if (WhiteOnMove(currentMove)) {
6339         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6340       } else {
6341         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6342       }
6343       break;
6344     case MT_STALEMATE:
6345       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6346       break;
6347     }
6348     break;
6349
6350   case MachinePlaysBlack:
6351   case MachinePlaysWhite:
6352     /* disable certain menu options while machine is thinking */
6353     SetMachineThinkingEnables();
6354     break;
6355
6356   default:
6357     break;
6358   }
6359
6360   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6361   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6362
6363   if(bookHit) { // [HGM] book: simulate book reply
6364         static char bookMove[MSG_SIZ]; // a bit generous?
6365
6366         programStats.nodes = programStats.depth = programStats.time =
6367         programStats.score = programStats.got_only_move = 0;
6368         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6369
6370         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6371         strcat(bookMove, bookHit);
6372         HandleMachineMove(bookMove, &first);
6373   }
6374   return 1;
6375 }
6376
6377 void
6378 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6379      Board board;
6380      int flags;
6381      ChessMove kind;
6382      int rf, ff, rt, ft;
6383      VOIDSTAR closure;
6384 {
6385     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6386     Markers *m = (Markers *) closure;
6387     if(rf == fromY && ff == fromX)
6388         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6389                          || kind == WhiteCapturesEnPassant
6390                          || kind == BlackCapturesEnPassant);
6391     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6392 }
6393
6394 void
6395 MarkTargetSquares(int clear)
6396 {
6397   int x, y;
6398   if(!appData.markers || !appData.highlightDragging ||
6399      !appData.testLegality || gameMode == EditPosition) return;
6400   if(clear) {
6401     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6402   } else {
6403     int capt = 0;
6404     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6405     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6406       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6407       if(capt)
6408       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6409     }
6410   }
6411   DrawPosition(TRUE, NULL);
6412 }
6413
6414 int
6415 Explode(Board board, int fromX, int fromY, int toX, int toY)
6416 {
6417     if(gameInfo.variant == VariantAtomic &&
6418        (board[toY][toX] != EmptySquare ||                     // capture?
6419         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6420                          board[fromY][fromX] == BlackPawn   )
6421       )) {
6422         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6423         return TRUE;
6424     }
6425     return FALSE;
6426 }
6427
6428 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6429
6430 int CanPromote(ChessSquare piece, int y)
6431 {
6432         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6433         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6434         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6435            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6436            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6437                                                   gameInfo.variant == VariantMakruk) return FALSE;
6438         return (piece == BlackPawn && y == 1 ||
6439                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6440                 piece == BlackLance && y == 1 ||
6441                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6442 }
6443
6444 void LeftClick(ClickType clickType, int xPix, int yPix)
6445 {
6446     int x, y;
6447     Boolean saveAnimate;
6448     static int second = 0, promotionChoice = 0;
6449     char promoChoice = NULLCHAR;
6450     ChessSquare piece;
6451
6452     if(appData.seekGraph && appData.icsActive && loggedOn &&
6453         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6454         SeekGraphClick(clickType, xPix, yPix, 0);
6455         return;
6456     }
6457
6458     if (clickType == Press) ErrorPopDown();
6459     MarkTargetSquares(1);
6460
6461     x = EventToSquare(xPix, BOARD_WIDTH);
6462     y = EventToSquare(yPix, BOARD_HEIGHT);
6463     if (!flipView && y >= 0) {
6464         y = BOARD_HEIGHT - 1 - y;
6465     }
6466     if (flipView && x >= 0) {
6467         x = BOARD_WIDTH - 1 - x;
6468     }
6469
6470     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6471         defaultPromoChoice = promoSweep;
6472         promoSweep = EmptySquare;   // terminate sweep
6473         promoDefaultAltered = TRUE;
6474         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6475     }
6476
6477     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6478         if(clickType == Release) return; // ignore upclick of click-click destination
6479         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6480         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6481         if(gameInfo.holdingsWidth &&
6482                 (WhiteOnMove(currentMove)
6483                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6484                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6485             // click in right holdings, for determining promotion piece
6486             ChessSquare p = boards[currentMove][y][x];
6487             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6488             if(p != EmptySquare) {
6489                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6490                 fromX = fromY = -1;
6491                 return;
6492             }
6493         }
6494         DrawPosition(FALSE, boards[currentMove]);
6495         return;
6496     }
6497
6498     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6499     if(clickType == Press
6500             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6501               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6502               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6503         return;
6504
6505     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6506         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6507
6508     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6509         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6510                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6511         defaultPromoChoice = DefaultPromoChoice(side);
6512     }
6513
6514     autoQueen = appData.alwaysPromoteToQueen;
6515
6516     if (fromX == -1) {
6517       int originalY = y;
6518       gatingPiece = EmptySquare;
6519       if (clickType != Press) {
6520         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6521             DragPieceEnd(xPix, yPix); dragging = 0;
6522             DrawPosition(FALSE, NULL);
6523         }
6524         return;
6525       }
6526       fromX = x; fromY = y;
6527       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6528          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6529          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6530             /* First square */
6531             if (OKToStartUserMove(fromX, fromY)) {
6532                 second = 0;
6533                 MarkTargetSquares(0);
6534                 DragPieceBegin(xPix, yPix); dragging = 1;
6535                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6536                     promoSweep = defaultPromoChoice;
6537                     selectFlag = 0; lastX = xPix; lastY = yPix;
6538                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6539                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6540                 }
6541                 if (appData.highlightDragging) {
6542                     SetHighlights(fromX, fromY, -1, -1);
6543                 }
6544             }
6545             return;
6546         }
6547     }
6548
6549     /* fromX != -1 */
6550     if (clickType == Press && gameMode != EditPosition) {
6551         ChessSquare fromP;
6552         ChessSquare toP;
6553         int frc;
6554
6555         // ignore off-board to clicks
6556         if(y < 0 || x < 0) return;
6557
6558         /* Check if clicking again on the same color piece */
6559         fromP = boards[currentMove][fromY][fromX];
6560         toP = boards[currentMove][y][x];
6561         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6562         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6563              WhitePawn <= toP && toP <= WhiteKing &&
6564              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6565              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6566             (BlackPawn <= fromP && fromP <= BlackKing &&
6567              BlackPawn <= toP && toP <= BlackKing &&
6568              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6569              !(fromP == BlackKing && toP == BlackRook && frc))) {
6570             /* Clicked again on same color piece -- changed his mind */
6571             second = (x == fromX && y == fromY);
6572             promoDefaultAltered = FALSE;
6573            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6574             if (appData.highlightDragging) {
6575                 SetHighlights(x, y, -1, -1);
6576             } else {
6577                 ClearHighlights();
6578             }
6579             if (OKToStartUserMove(x, y)) {
6580                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6581                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6582                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6583                  gatingPiece = boards[currentMove][fromY][fromX];
6584                 else gatingPiece = EmptySquare;
6585                 fromX = x;
6586                 fromY = y; dragging = 1;
6587                 MarkTargetSquares(0);
6588                 DragPieceBegin(xPix, yPix);
6589                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6590                     promoSweep = defaultPromoChoice;
6591                     selectFlag = 0; lastX = xPix; lastY = yPix;
6592                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6593                 }
6594             }
6595            }
6596            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6597            second = FALSE; 
6598         }
6599         // ignore clicks on holdings
6600         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6601     }
6602
6603     if (clickType == Release && x == fromX && y == fromY) {
6604         DragPieceEnd(xPix, yPix); dragging = 0;
6605         if (appData.animateDragging) {
6606             /* Undo animation damage if any */
6607             DrawPosition(FALSE, NULL);
6608         }
6609         if (second) {
6610             /* Second up/down in same square; just abort move */
6611             second = 0;
6612             fromX = fromY = -1;
6613             gatingPiece = EmptySquare;
6614             ClearHighlights();
6615             gotPremove = 0;
6616             ClearPremoveHighlights();
6617         } else {
6618             /* First upclick in same square; start click-click mode */
6619             SetHighlights(x, y, -1, -1);
6620         }
6621         return;
6622     }
6623
6624     /* we now have a different from- and (possibly off-board) to-square */
6625     /* Completed move */
6626     toX = x;
6627     toY = y;
6628     saveAnimate = appData.animate;
6629     if (clickType == Press) {
6630         /* Finish clickclick move */
6631         if (appData.animate || appData.highlightLastMove) {
6632             SetHighlights(fromX, fromY, toX, toY);
6633         } else {
6634             ClearHighlights();
6635         }
6636     } else {
6637         /* Finish drag move */
6638         if (appData.highlightLastMove) {
6639             SetHighlights(fromX, fromY, toX, toY);
6640         } else {
6641             ClearHighlights();
6642         }
6643         DragPieceEnd(xPix, yPix); dragging = 0;
6644         /* Don't animate move and drag both */
6645         appData.animate = FALSE;
6646     }
6647
6648     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6649     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6650         ChessSquare piece = boards[currentMove][fromY][fromX];
6651         if(gameMode == EditPosition && piece != EmptySquare &&
6652            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6653             int n;
6654
6655             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6656                 n = PieceToNumber(piece - (int)BlackPawn);
6657                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6658                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6659                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6660             } else
6661             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6662                 n = PieceToNumber(piece);
6663                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6664                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6665                 boards[currentMove][n][BOARD_WIDTH-2]++;
6666             }
6667             boards[currentMove][fromY][fromX] = EmptySquare;
6668         }
6669         ClearHighlights();
6670         fromX = fromY = -1;
6671         DrawPosition(TRUE, boards[currentMove]);
6672         return;
6673     }
6674
6675     // off-board moves should not be highlighted
6676     if(x < 0 || y < 0) ClearHighlights();
6677
6678     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6679
6680     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6681         SetHighlights(fromX, fromY, toX, toY);
6682         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6683             // [HGM] super: promotion to captured piece selected from holdings
6684             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6685             promotionChoice = TRUE;
6686             // kludge follows to temporarily execute move on display, without promoting yet
6687             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6688             boards[currentMove][toY][toX] = p;
6689             DrawPosition(FALSE, boards[currentMove]);
6690             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6691             boards[currentMove][toY][toX] = q;
6692             DisplayMessage("Click in holdings to choose piece", "");
6693             return;
6694         }
6695         PromotionPopUp();
6696     } else {
6697         int oldMove = currentMove;
6698         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6699         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6700         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6701         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6702            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6703             DrawPosition(TRUE, boards[currentMove]);
6704         fromX = fromY = -1;
6705     }
6706     appData.animate = saveAnimate;
6707     if (appData.animate || appData.animateDragging) {
6708         /* Undo animation damage if needed */
6709         DrawPosition(FALSE, NULL);
6710     }
6711 }
6712
6713 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6714 {   // front-end-free part taken out of PieceMenuPopup
6715     int whichMenu; int xSqr, ySqr;
6716
6717     if(seekGraphUp) { // [HGM] seekgraph
6718         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6719         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6720         return -2;
6721     }
6722
6723     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6724          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6725         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6726         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6727         if(action == Press)   {
6728             originalFlip = flipView;
6729             flipView = !flipView; // temporarily flip board to see game from partners perspective
6730             DrawPosition(TRUE, partnerBoard);
6731             DisplayMessage(partnerStatus, "");
6732             partnerUp = TRUE;
6733         } else if(action == Release) {
6734             flipView = originalFlip;
6735             DrawPosition(TRUE, boards[currentMove]);
6736             partnerUp = FALSE;
6737         }
6738         return -2;
6739     }
6740
6741     xSqr = EventToSquare(x, BOARD_WIDTH);
6742     ySqr = EventToSquare(y, BOARD_HEIGHT);
6743     if (action == Release) {
6744         if(pieceSweep != EmptySquare) {
6745             EditPositionMenuEvent(pieceSweep, toX, toY);
6746             pieceSweep = EmptySquare;
6747         } else UnLoadPV(); // [HGM] pv
6748     }
6749     if (action != Press) return -2; // return code to be ignored
6750     switch (gameMode) {
6751       case IcsExamining:
6752         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6753       case EditPosition:
6754         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6755         if (xSqr < 0 || ySqr < 0) return -1;
6756         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6757         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6758         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6759         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6760         NextPiece(0);
6761         return -2;\r
6762       case IcsObserving:
6763         if(!appData.icsEngineAnalyze) return -1;
6764       case IcsPlayingWhite:
6765       case IcsPlayingBlack:
6766         if(!appData.zippyPlay) goto noZip;
6767       case AnalyzeMode:
6768       case AnalyzeFile:
6769       case MachinePlaysWhite:
6770       case MachinePlaysBlack:
6771       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6772         if (!appData.dropMenu) {
6773           LoadPV(x, y);
6774           return 2; // flag front-end to grab mouse events
6775         }
6776         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6777            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6778       case EditGame:
6779       noZip:
6780         if (xSqr < 0 || ySqr < 0) return -1;
6781         if (!appData.dropMenu || appData.testLegality &&
6782             gameInfo.variant != VariantBughouse &&
6783             gameInfo.variant != VariantCrazyhouse) return -1;
6784         whichMenu = 1; // drop menu
6785         break;
6786       default:
6787         return -1;
6788     }
6789
6790     if (((*fromX = xSqr) < 0) ||
6791         ((*fromY = ySqr) < 0)) {
6792         *fromX = *fromY = -1;
6793         return -1;
6794     }
6795     if (flipView)
6796       *fromX = BOARD_WIDTH - 1 - *fromX;
6797     else
6798       *fromY = BOARD_HEIGHT - 1 - *fromY;
6799
6800     return whichMenu;
6801 }
6802
6803 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6804 {
6805 //    char * hint = lastHint;
6806     FrontEndProgramStats stats;
6807
6808     stats.which = cps == &first ? 0 : 1;
6809     stats.depth = cpstats->depth;
6810     stats.nodes = cpstats->nodes;
6811     stats.score = cpstats->score;
6812     stats.time = cpstats->time;
6813     stats.pv = cpstats->movelist;
6814     stats.hint = lastHint;
6815     stats.an_move_index = 0;
6816     stats.an_move_count = 0;
6817
6818     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6819         stats.hint = cpstats->move_name;
6820         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6821         stats.an_move_count = cpstats->nr_moves;
6822     }
6823
6824     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
6825
6826     SetProgramStats( &stats );
6827 }
6828
6829 void
6830 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6831 {       // count all piece types
6832         int p, f, r;
6833         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6834         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6835         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6836                 p = board[r][f];
6837                 pCnt[p]++;
6838                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6839                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6840                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6841                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6842                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6843                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6844         }
6845 }
6846
6847 int
6848 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6849 {
6850         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6851         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6852
6853         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6854         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6855         if(myPawns == 2 && nMine == 3) // KPP
6856             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6857         if(myPawns == 1 && nMine == 2) // KP
6858             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6859         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6860             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6861         if(myPawns) return FALSE;
6862         if(pCnt[WhiteRook+side])
6863             return pCnt[BlackRook-side] ||
6864                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6865                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6866                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6867         if(pCnt[WhiteCannon+side]) {
6868             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6869             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6870         }
6871         if(pCnt[WhiteKnight+side])
6872             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6873         return FALSE;
6874 }
6875
6876 int
6877 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6878 {
6879         VariantClass v = gameInfo.variant;
6880
6881         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6882         if(v == VariantShatranj) return TRUE; // always winnable through baring
6883         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6884         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6885
6886         if(v == VariantXiangqi) {
6887                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6888
6889                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6890                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6891                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6892                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6893                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6894                 if(stale) // we have at least one last-rank P plus perhaps C
6895                     return majors // KPKX
6896                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6897                 else // KCA*E*
6898                     return pCnt[WhiteFerz+side] // KCAK
6899                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6900                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6901                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6902
6903         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6904                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6905
6906                 if(nMine == 1) return FALSE; // bare King
6907                 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
6908                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6909                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6910                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6911                 if(pCnt[WhiteKnight+side])
6912                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6913                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6914                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6915                 if(nBishops)
6916                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6917                 if(pCnt[WhiteAlfil+side])
6918                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6919                 if(pCnt[WhiteWazir+side])
6920                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6921         }
6922
6923         return TRUE;
6924 }
6925
6926 int
6927 Adjudicate(ChessProgramState *cps)
6928 {       // [HGM] some adjudications useful with buggy engines
6929         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6930         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6931         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6932         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6933         int k, count = 0; static int bare = 1;
6934         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6935         Boolean canAdjudicate = !appData.icsActive;
6936
6937         // most tests only when we understand the game, i.e. legality-checking on
6938             if( appData.testLegality )
6939             {   /* [HGM] Some more adjudications for obstinate engines */
6940                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6941                 static int moveCount = 6;
6942                 ChessMove result;
6943                 char *reason = NULL;
6944
6945                 /* Count what is on board. */
6946                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6947
6948                 /* Some material-based adjudications that have to be made before stalemate test */
6949                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6950                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6951                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6952                      if(canAdjudicate && appData.checkMates) {
6953                          if(engineOpponent)
6954                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6955                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6956                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6957                          return 1;
6958                      }
6959                 }
6960
6961                 /* Bare King in Shatranj (loses) or Losers (wins) */
6962                 if( nrW == 1 || nrB == 1) {
6963                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6964                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6965                      if(canAdjudicate && appData.checkMates) {
6966                          if(engineOpponent)
6967                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6968                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6969                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6970                          return 1;
6971                      }
6972                   } else
6973                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6974                   {    /* bare King */
6975                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6976                         if(canAdjudicate && appData.checkMates) {
6977                             /* but only adjudicate if adjudication enabled */
6978                             if(engineOpponent)
6979                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6980                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6981                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6982                             return 1;
6983                         }
6984                   }
6985                 } else bare = 1;
6986
6987
6988             // don't wait for engine to announce game end if we can judge ourselves
6989             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6990               case MT_CHECK:
6991                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6992                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6993                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6994                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6995                             checkCnt++;
6996                         if(checkCnt >= 2) {
6997                             reason = "Xboard adjudication: 3rd check";
6998                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6999                             break;
7000                         }
7001                     }
7002                 }
7003               case MT_NONE:
7004               default:
7005                 break;
7006               case MT_STALEMATE:
7007               case MT_STAINMATE:
7008                 reason = "Xboard adjudication: Stalemate";
7009                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7010                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7011                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7012                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7013                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7014                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7015                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7016                                                                         EP_CHECKMATE : EP_WINS);
7017                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7018                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7019                 }
7020                 break;
7021               case MT_CHECKMATE:
7022                 reason = "Xboard adjudication: Checkmate";
7023                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7024                 break;
7025             }
7026
7027                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7028                     case EP_STALEMATE:
7029                         result = GameIsDrawn; break;
7030                     case EP_CHECKMATE:
7031                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7032                     case EP_WINS:
7033                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7034                     default:
7035                         result = EndOfFile;
7036                 }
7037                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7038                     if(engineOpponent)
7039                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7040                     GameEnds( result, reason, GE_XBOARD );
7041                     return 1;
7042                 }
7043
7044                 /* Next absolutely insufficient mating material. */
7045                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7046                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7047                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7048
7049                      /* always flag draws, for judging claims */
7050                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7051
7052                      if(canAdjudicate && appData.materialDraws) {
7053                          /* but only adjudicate them if adjudication enabled */
7054                          if(engineOpponent) {
7055                            SendToProgram("force\n", engineOpponent); // suppress reply
7056                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7057                          }
7058                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7059                          return 1;
7060                      }
7061                 }
7062
7063                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7064                 if(gameInfo.variant == VariantXiangqi ?
7065                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7066                  : nrW + nrB == 4 &&
7067                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7068                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7069                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7070                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7071                    ) ) {
7072                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7073                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7074                           if(engineOpponent) {
7075                             SendToProgram("force\n", engineOpponent); // suppress reply
7076                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7077                           }
7078                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7079                           return 1;
7080                      }
7081                 } else moveCount = 6;
7082             }
7083         if (appData.debugMode) { int i;
7084             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7085                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7086                     appData.drawRepeats);
7087             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7088               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7089
7090         }
7091
7092         // Repetition draws and 50-move rule can be applied independently of legality testing
7093
7094                 /* Check for rep-draws */
7095                 count = 0;
7096                 for(k = forwardMostMove-2;
7097                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7098                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7099                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7100                     k-=2)
7101                 {   int rights=0;
7102                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7103                         /* compare castling rights */
7104                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7105                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7106                                 rights++; /* King lost rights, while rook still had them */
7107                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7108                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7109                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7110                                    rights++; /* but at least one rook lost them */
7111                         }
7112                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7113                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7114                                 rights++;
7115                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7116                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7117                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7118                                    rights++;
7119                         }
7120                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7121                             && appData.drawRepeats > 1) {
7122                              /* adjudicate after user-specified nr of repeats */
7123                              int result = GameIsDrawn;
7124                              char *details = "XBoard adjudication: repetition draw";
7125                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7126                                 // [HGM] xiangqi: check for forbidden perpetuals
7127                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7128                                 for(m=forwardMostMove; m>k; m-=2) {
7129                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7130                                         ourPerpetual = 0; // the current mover did not always check
7131                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7132                                         hisPerpetual = 0; // the opponent did not always check
7133                                 }
7134                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7135                                                                         ourPerpetual, hisPerpetual);
7136                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7137                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7138                                     details = "Xboard adjudication: perpetual checking";
7139                                 } else
7140                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7141                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7142                                 } else
7143                                 // Now check for perpetual chases
7144                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7145                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7146                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7147                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7148                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7149                                         details = "Xboard adjudication: perpetual chasing";
7150                                     } else
7151                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7152                                         break; // Abort repetition-checking loop.
7153                                 }
7154                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7155                              }
7156                              if(engineOpponent) {
7157                                SendToProgram("force\n", engineOpponent); // suppress reply
7158                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7159                              }
7160                              GameEnds( result, details, GE_XBOARD );
7161                              return 1;
7162                         }
7163                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7164                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7165                     }
7166                 }
7167
7168                 /* Now we test for 50-move draws. Determine ply count */
7169                 count = forwardMostMove;
7170                 /* look for last irreversble move */
7171                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7172                     count--;
7173                 /* if we hit starting position, add initial plies */
7174                 if( count == backwardMostMove )
7175                     count -= initialRulePlies;
7176                 count = forwardMostMove - count;
7177                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7178                         // adjust reversible move counter for checks in Xiangqi
7179                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7180                         if(i < backwardMostMove) i = backwardMostMove;
7181                         while(i <= forwardMostMove) {
7182                                 lastCheck = inCheck; // check evasion does not count
7183                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7184                                 if(inCheck || lastCheck) count--; // check does not count
7185                                 i++;
7186                         }
7187                 }
7188                 if( count >= 100)
7189                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7190                          /* this is used to judge if draw claims are legal */
7191                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7192                          if(engineOpponent) {
7193                            SendToProgram("force\n", engineOpponent); // suppress reply
7194                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7195                          }
7196                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7197                          return 1;
7198                 }
7199
7200                 /* if draw offer is pending, treat it as a draw claim
7201                  * when draw condition present, to allow engines a way to
7202                  * claim draws before making their move to avoid a race
7203                  * condition occurring after their move
7204                  */
7205                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7206                          char *p = NULL;
7207                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7208                              p = "Draw claim: 50-move rule";
7209                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7210                              p = "Draw claim: 3-fold repetition";
7211                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7212                              p = "Draw claim: insufficient mating material";
7213                          if( p != NULL && canAdjudicate) {
7214                              if(engineOpponent) {
7215                                SendToProgram("force\n", engineOpponent); // suppress reply
7216                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7217                              }
7218                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7219                              return 1;
7220                          }
7221                 }
7222
7223                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7224                     if(engineOpponent) {
7225                       SendToProgram("force\n", engineOpponent); // suppress reply
7226                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7227                     }
7228                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7229                     return 1;
7230                 }
7231         return 0;
7232 }
7233
7234 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7235 {   // [HGM] book: this routine intercepts moves to simulate book replies
7236     char *bookHit = NULL;
7237
7238     //first determine if the incoming move brings opponent into his book
7239     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7240         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7241     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7242     if(bookHit != NULL && !cps->bookSuspend) {
7243         // make sure opponent is not going to reply after receiving move to book position
7244         SendToProgram("force\n", cps);
7245         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7246     }
7247     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7248     // now arrange restart after book miss
7249     if(bookHit) {
7250         // after a book hit we never send 'go', and the code after the call to this routine
7251         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7252         char buf[MSG_SIZ];
7253         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7254         SendToProgram(buf, cps);
7255         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7256     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7257         SendToProgram("go\n", cps);
7258         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7259     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7260         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7261             SendToProgram("go\n", cps);
7262         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7263     }
7264     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7265 }
7266
7267 char *savedMessage;
7268 ChessProgramState *savedState;
7269 void DeferredBookMove(void)
7270 {
7271         if(savedState->lastPing != savedState->lastPong)
7272                     ScheduleDelayedEvent(DeferredBookMove, 10);
7273         else
7274         HandleMachineMove(savedMessage, savedState);
7275 }
7276
7277 void
7278 HandleMachineMove(message, cps)
7279      char *message;
7280      ChessProgramState *cps;
7281 {
7282     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7283     char realname[MSG_SIZ];
7284     int fromX, fromY, toX, toY;
7285     ChessMove moveType;
7286     char promoChar;
7287     char *p;
7288     int machineWhite;
7289     char *bookHit;
7290
7291     cps->userError = 0;
7292
7293 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7294     /*
7295      * Kludge to ignore BEL characters
7296      */
7297     while (*message == '\007') message++;
7298
7299     /*
7300      * [HGM] engine debug message: ignore lines starting with '#' character
7301      */
7302     if(cps->debug && *message == '#') return;
7303
7304     /*
7305      * Look for book output
7306      */
7307     if (cps == &first && bookRequested) {
7308         if (message[0] == '\t' || message[0] == ' ') {
7309             /* Part of the book output is here; append it */
7310             strcat(bookOutput, message);
7311             strcat(bookOutput, "  \n");
7312             return;
7313         } else if (bookOutput[0] != NULLCHAR) {
7314             /* All of book output has arrived; display it */
7315             char *p = bookOutput;
7316             while (*p != NULLCHAR) {
7317                 if (*p == '\t') *p = ' ';
7318                 p++;
7319             }
7320             DisplayInformation(bookOutput);
7321             bookRequested = FALSE;
7322             /* Fall through to parse the current output */
7323         }
7324     }
7325
7326     /*
7327      * Look for machine move.
7328      */
7329     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7330         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7331     {
7332         /* This method is only useful on engines that support ping */
7333         if (cps->lastPing != cps->lastPong) {
7334           if (gameMode == BeginningOfGame) {
7335             /* Extra move from before last new; ignore */
7336             if (appData.debugMode) {
7337                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7338             }
7339           } else {
7340             if (appData.debugMode) {
7341                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7342                         cps->which, gameMode);
7343             }
7344
7345             SendToProgram("undo\n", cps);
7346           }
7347           return;
7348         }
7349
7350         switch (gameMode) {
7351           case BeginningOfGame:
7352             /* Extra move from before last reset; ignore */
7353             if (appData.debugMode) {
7354                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7355             }
7356             return;
7357
7358           case EndOfGame:
7359           case IcsIdle:
7360           default:
7361             /* Extra move after we tried to stop.  The mode test is
7362                not a reliable way of detecting this problem, but it's
7363                the best we can do on engines that don't support ping.
7364             */
7365             if (appData.debugMode) {
7366                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7367                         cps->which, gameMode);
7368             }
7369             SendToProgram("undo\n", cps);
7370             return;
7371
7372           case MachinePlaysWhite:
7373           case IcsPlayingWhite:
7374             machineWhite = TRUE;
7375             break;
7376
7377           case MachinePlaysBlack:
7378           case IcsPlayingBlack:
7379             machineWhite = FALSE;
7380             break;
7381
7382           case TwoMachinesPlay:
7383             machineWhite = (cps->twoMachinesColor[0] == 'w');
7384             break;
7385         }
7386         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7387             if (appData.debugMode) {
7388                 fprintf(debugFP,
7389                         "Ignoring move out of turn by %s, gameMode %d"
7390                         ", forwardMost %d\n",
7391                         cps->which, gameMode, forwardMostMove);
7392             }
7393             return;
7394         }
7395
7396     if (appData.debugMode) { int f = forwardMostMove;
7397         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7398                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7399                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7400     }
7401         if(cps->alphaRank) AlphaRank(machineMove, 4);
7402         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7403                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7404             /* Machine move could not be parsed; ignore it. */
7405           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7406                     machineMove, _(cps->which));
7407             DisplayError(buf1, 0);
7408             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7409                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7410             if (gameMode == TwoMachinesPlay) {
7411               GameEnds(machineWhite ? BlackWins : WhiteWins,
7412                        buf1, GE_XBOARD);
7413             }
7414             return;
7415         }
7416
7417         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7418         /* So we have to redo legality test with true e.p. status here,  */
7419         /* to make sure an illegal e.p. capture does not slip through,   */
7420         /* to cause a forfeit on a justified illegal-move complaint      */
7421         /* of the opponent.                                              */
7422         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7423            ChessMove moveType;
7424            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7425                              fromY, fromX, toY, toX, promoChar);
7426             if (appData.debugMode) {
7427                 int i;
7428                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7429                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7430                 fprintf(debugFP, "castling rights\n");
7431             }
7432             if(moveType == IllegalMove) {
7433               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7434                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7435                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7436                            buf1, GE_XBOARD);
7437                 return;
7438            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7439            /* [HGM] Kludge to handle engines that send FRC-style castling
7440               when they shouldn't (like TSCP-Gothic) */
7441            switch(moveType) {
7442              case WhiteASideCastleFR:
7443              case BlackASideCastleFR:
7444                toX+=2;
7445                currentMoveString[2]++;
7446                break;
7447              case WhiteHSideCastleFR:
7448              case BlackHSideCastleFR:
7449                toX--;
7450                currentMoveString[2]--;
7451                break;
7452              default: ; // nothing to do, but suppresses warning of pedantic compilers
7453            }
7454         }
7455         hintRequested = FALSE;
7456         lastHint[0] = NULLCHAR;
7457         bookRequested = FALSE;
7458         /* Program may be pondering now */
7459         cps->maybeThinking = TRUE;
7460         if (cps->sendTime == 2) cps->sendTime = 1;
7461         if (cps->offeredDraw) cps->offeredDraw--;
7462
7463         /* [AS] Save move info*/
7464         pvInfoList[ forwardMostMove ].score = programStats.score;
7465         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7466         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7467
7468         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7469
7470         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7471         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7472             int count = 0;
7473
7474             while( count < adjudicateLossPlies ) {
7475                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7476
7477                 if( count & 1 ) {
7478                     score = -score; /* Flip score for winning side */
7479                 }
7480
7481                 if( score > adjudicateLossThreshold ) {
7482                     break;
7483                 }
7484
7485                 count++;
7486             }
7487
7488             if( count >= adjudicateLossPlies ) {
7489                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7490
7491                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7492                     "Xboard adjudication",
7493                     GE_XBOARD );
7494
7495                 return;
7496             }
7497         }
7498
7499         if(Adjudicate(cps)) {
7500             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7501             return; // [HGM] adjudicate: for all automatic game ends
7502         }
7503
7504 #if ZIPPY
7505         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7506             first.initDone) {
7507           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7508                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7509                 SendToICS("draw ");
7510                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7511           }
7512           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7513           ics_user_moved = 1;
7514           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7515                 char buf[3*MSG_SIZ];
7516
7517                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7518                         programStats.score / 100.,
7519                         programStats.depth,
7520                         programStats.time / 100.,
7521                         (unsigned int)programStats.nodes,
7522                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7523                         programStats.movelist);
7524                 SendToICS(buf);
7525 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7526           }
7527         }
7528 #endif
7529
7530         /* [AS] Clear stats for next move */
7531         ClearProgramStats();
7532         thinkOutput[0] = NULLCHAR;
7533         hiddenThinkOutputState = 0;
7534
7535         bookHit = NULL;
7536         if (gameMode == TwoMachinesPlay) {
7537             /* [HGM] relaying draw offers moved to after reception of move */
7538             /* and interpreting offer as claim if it brings draw condition */
7539             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7540                 SendToProgram("draw\n", cps->other);
7541             }
7542             if (cps->other->sendTime) {
7543                 SendTimeRemaining(cps->other,
7544                                   cps->other->twoMachinesColor[0] == 'w');
7545             }
7546             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7547             if (firstMove && !bookHit) {
7548                 firstMove = FALSE;
7549                 if (cps->other->useColors) {
7550                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7551                 }
7552                 SendToProgram("go\n", cps->other);
7553             }
7554             cps->other->maybeThinking = TRUE;
7555         }
7556
7557         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7558
7559         if (!pausing && appData.ringBellAfterMoves) {
7560             RingBell();
7561         }
7562
7563         /*
7564          * Reenable menu items that were disabled while
7565          * machine was thinking
7566          */
7567         if (gameMode != TwoMachinesPlay)
7568             SetUserThinkingEnables();
7569
7570         // [HGM] book: after book hit opponent has received move and is now in force mode
7571         // force the book reply into it, and then fake that it outputted this move by jumping
7572         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7573         if(bookHit) {
7574                 static char bookMove[MSG_SIZ]; // a bit generous?
7575
7576                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7577                 strcat(bookMove, bookHit);
7578                 message = bookMove;
7579                 cps = cps->other;
7580                 programStats.nodes = programStats.depth = programStats.time =
7581                 programStats.score = programStats.got_only_move = 0;
7582                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7583
7584                 if(cps->lastPing != cps->lastPong) {
7585                     savedMessage = message; // args for deferred call
7586                     savedState = cps;
7587                     ScheduleDelayedEvent(DeferredBookMove, 10);
7588                     return;
7589                 }
7590                 goto FakeBookMove;
7591         }
7592
7593         return;
7594     }
7595
7596     /* Set special modes for chess engines.  Later something general
7597      *  could be added here; for now there is just one kludge feature,
7598      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7599      *  when "xboard" is given as an interactive command.
7600      */
7601     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7602         cps->useSigint = FALSE;
7603         cps->useSigterm = FALSE;
7604     }
7605     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7606       ParseFeatures(message+8, cps);
7607       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7608     }
7609
7610     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7611       int dummy, s=6; char buf[MSG_SIZ];
7612       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7613       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7614       ParseFEN(boards[0], &dummy, message+s);
7615       DrawPosition(TRUE, boards[0]);
7616       startedFromSetupPosition = TRUE;
7617       return;
7618     }
7619     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7620      * want this, I was asked to put it in, and obliged.
7621      */
7622     if (!strncmp(message, "setboard ", 9)) {
7623         Board initial_position;
7624
7625         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7626
7627         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7628             DisplayError(_("Bad FEN received from engine"), 0);
7629             return ;
7630         } else {
7631            Reset(TRUE, FALSE);
7632            CopyBoard(boards[0], initial_position);
7633            initialRulePlies = FENrulePlies;
7634            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7635            else gameMode = MachinePlaysBlack;
7636            DrawPosition(FALSE, boards[currentMove]);
7637         }
7638         return;
7639     }
7640
7641     /*
7642      * Look for communication commands
7643      */
7644     if (!strncmp(message, "telluser ", 9)) {
7645         if(message[9] == '\\' && message[10] == '\\')
7646             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7647         DisplayNote(message + 9);
7648         return;
7649     }
7650     if (!strncmp(message, "tellusererror ", 14)) {
7651         cps->userError = 1;
7652         if(message[14] == '\\' && message[15] == '\\')
7653             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7654         DisplayError(message + 14, 0);
7655         return;
7656     }
7657     if (!strncmp(message, "tellopponent ", 13)) {
7658       if (appData.icsActive) {
7659         if (loggedOn) {
7660           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7661           SendToICS(buf1);
7662         }
7663       } else {
7664         DisplayNote(message + 13);
7665       }
7666       return;
7667     }
7668     if (!strncmp(message, "tellothers ", 11)) {
7669       if (appData.icsActive) {
7670         if (loggedOn) {
7671           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7672           SendToICS(buf1);
7673         }
7674       }
7675       return;
7676     }
7677     if (!strncmp(message, "tellall ", 8)) {
7678       if (appData.icsActive) {
7679         if (loggedOn) {
7680           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7681           SendToICS(buf1);
7682         }
7683       } else {
7684         DisplayNote(message + 8);
7685       }
7686       return;
7687     }
7688     if (strncmp(message, "warning", 7) == 0) {
7689         /* Undocumented feature, use tellusererror in new code */
7690         DisplayError(message, 0);
7691         return;
7692     }
7693     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7694         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7695         strcat(realname, " query");
7696         AskQuestion(realname, buf2, buf1, cps->pr);
7697         return;
7698     }
7699     /* Commands from the engine directly to ICS.  We don't allow these to be
7700      *  sent until we are logged on. Crafty kibitzes have been known to
7701      *  interfere with the login process.
7702      */
7703     if (loggedOn) {
7704         if (!strncmp(message, "tellics ", 8)) {
7705             SendToICS(message + 8);
7706             SendToICS("\n");
7707             return;
7708         }
7709         if (!strncmp(message, "tellicsnoalias ", 15)) {
7710             SendToICS(ics_prefix);
7711             SendToICS(message + 15);
7712             SendToICS("\n");
7713             return;
7714         }
7715         /* The following are for backward compatibility only */
7716         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7717             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7718             SendToICS(ics_prefix);
7719             SendToICS(message);
7720             SendToICS("\n");
7721             return;
7722         }
7723     }
7724     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7725         return;
7726     }
7727     /*
7728      * If the move is illegal, cancel it and redraw the board.
7729      * Also deal with other error cases.  Matching is rather loose
7730      * here to accommodate engines written before the spec.
7731      */
7732     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7733         strncmp(message, "Error", 5) == 0) {
7734         if (StrStr(message, "name") ||
7735             StrStr(message, "rating") || StrStr(message, "?") ||
7736             StrStr(message, "result") || StrStr(message, "board") ||
7737             StrStr(message, "bk") || StrStr(message, "computer") ||
7738             StrStr(message, "variant") || StrStr(message, "hint") ||
7739             StrStr(message, "random") || StrStr(message, "depth") ||
7740             StrStr(message, "accepted")) {
7741             return;
7742         }
7743         if (StrStr(message, "protover")) {
7744           /* Program is responding to input, so it's apparently done
7745              initializing, and this error message indicates it is
7746              protocol version 1.  So we don't need to wait any longer
7747              for it to initialize and send feature commands. */
7748           FeatureDone(cps, 1);
7749           cps->protocolVersion = 1;
7750           return;
7751         }
7752         cps->maybeThinking = FALSE;
7753
7754         if (StrStr(message, "draw")) {
7755             /* Program doesn't have "draw" command */
7756             cps->sendDrawOffers = 0;
7757             return;
7758         }
7759         if (cps->sendTime != 1 &&
7760             (StrStr(message, "time") || StrStr(message, "otim"))) {
7761           /* Program apparently doesn't have "time" or "otim" command */
7762           cps->sendTime = 0;
7763           return;
7764         }
7765         if (StrStr(message, "analyze")) {
7766             cps->analysisSupport = FALSE;
7767             cps->analyzing = FALSE;
7768             Reset(FALSE, TRUE);
7769             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7770             DisplayError(buf2, 0);
7771             return;
7772         }
7773         if (StrStr(message, "(no matching move)st")) {
7774           /* Special kludge for GNU Chess 4 only */
7775           cps->stKludge = TRUE;
7776           SendTimeControl(cps, movesPerSession, timeControl,
7777                           timeIncrement, appData.searchDepth,
7778                           searchTime);
7779           return;
7780         }
7781         if (StrStr(message, "(no matching move)sd")) {
7782           /* Special kludge for GNU Chess 4 only */
7783           cps->sdKludge = TRUE;
7784           SendTimeControl(cps, movesPerSession, timeControl,
7785                           timeIncrement, appData.searchDepth,
7786                           searchTime);
7787           return;
7788         }
7789         if (!StrStr(message, "llegal")) {
7790             return;
7791         }
7792         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7793             gameMode == IcsIdle) return;
7794         if (forwardMostMove <= backwardMostMove) return;
7795         if (pausing) PauseEvent();
7796       if(appData.forceIllegal) {
7797             // [HGM] illegal: machine refused move; force position after move into it
7798           SendToProgram("force\n", cps);
7799           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7800                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7801                 // when black is to move, while there might be nothing on a2 or black
7802                 // might already have the move. So send the board as if white has the move.
7803                 // But first we must change the stm of the engine, as it refused the last move
7804                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7805                 if(WhiteOnMove(forwardMostMove)) {
7806                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7807                     SendBoard(cps, forwardMostMove); // kludgeless board
7808                 } else {
7809                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7810                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7811                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7812                 }
7813           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7814             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7815                  gameMode == TwoMachinesPlay)
7816               SendToProgram("go\n", cps);
7817             return;
7818       } else
7819         if (gameMode == PlayFromGameFile) {
7820             /* Stop reading this game file */
7821             gameMode = EditGame;
7822             ModeHighlight();
7823         }
7824         /* [HGM] illegal-move claim should forfeit game when Xboard */
7825         /* only passes fully legal moves                            */
7826         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7827             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7828                                 "False illegal-move claim", GE_XBOARD );
7829             return; // do not take back move we tested as valid
7830         }
7831         currentMove = forwardMostMove-1;
7832         DisplayMove(currentMove-1); /* before DisplayMoveError */
7833         SwitchClocks(forwardMostMove-1); // [HGM] race
7834         DisplayBothClocks();
7835         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7836                 parseList[currentMove], _(cps->which));
7837         DisplayMoveError(buf1);
7838         DrawPosition(FALSE, boards[currentMove]);
7839         return;
7840     }
7841     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7842         /* Program has a broken "time" command that
7843            outputs a string not ending in newline.
7844            Don't use it. */
7845         cps->sendTime = 0;
7846     }
7847
7848     /*
7849      * If chess program startup fails, exit with an error message.
7850      * Attempts to recover here are futile.
7851      */
7852     if ((StrStr(message, "unknown host") != NULL)
7853         || (StrStr(message, "No remote directory") != NULL)
7854         || (StrStr(message, "not found") != NULL)
7855         || (StrStr(message, "No such file") != NULL)
7856         || (StrStr(message, "can't alloc") != NULL)
7857         || (StrStr(message, "Permission denied") != NULL)) {
7858
7859         cps->maybeThinking = FALSE;
7860         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7861                 _(cps->which), cps->program, cps->host, message);
7862         RemoveInputSource(cps->isr);
7863         DisplayFatalError(buf1, 0, 1);
7864         return;
7865     }
7866
7867     /*
7868      * Look for hint output
7869      */
7870     if (sscanf(message, "Hint: %s", buf1) == 1) {
7871         if (cps == &first && hintRequested) {
7872             hintRequested = FALSE;
7873             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7874                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7875                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7876                                     PosFlags(forwardMostMove),
7877                                     fromY, fromX, toY, toX, promoChar, buf1);
7878                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7879                 DisplayInformation(buf2);
7880             } else {
7881                 /* Hint move could not be parsed!? */
7882               snprintf(buf2, sizeof(buf2),
7883                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7884                         buf1, _(cps->which));
7885                 DisplayError(buf2, 0);
7886             }
7887         } else {
7888           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7889         }
7890         return;
7891     }
7892
7893     /*
7894      * Ignore other messages if game is not in progress
7895      */
7896     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7897         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7898
7899     /*
7900      * look for win, lose, draw, or draw offer
7901      */
7902     if (strncmp(message, "1-0", 3) == 0) {
7903         char *p, *q, *r = "";
7904         p = strchr(message, '{');
7905         if (p) {
7906             q = strchr(p, '}');
7907             if (q) {
7908                 *q = NULLCHAR;
7909                 r = p + 1;
7910             }
7911         }
7912         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7913         return;
7914     } else if (strncmp(message, "0-1", 3) == 0) {
7915         char *p, *q, *r = "";
7916         p = strchr(message, '{');
7917         if (p) {
7918             q = strchr(p, '}');
7919             if (q) {
7920                 *q = NULLCHAR;
7921                 r = p + 1;
7922             }
7923         }
7924         /* Kludge for Arasan 4.1 bug */
7925         if (strcmp(r, "Black resigns") == 0) {
7926             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7927             return;
7928         }
7929         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7930         return;
7931     } else if (strncmp(message, "1/2", 3) == 0) {
7932         char *p, *q, *r = "";
7933         p = strchr(message, '{');
7934         if (p) {
7935             q = strchr(p, '}');
7936             if (q) {
7937                 *q = NULLCHAR;
7938                 r = p + 1;
7939             }
7940         }
7941
7942         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7943         return;
7944
7945     } else if (strncmp(message, "White resign", 12) == 0) {
7946         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7947         return;
7948     } else if (strncmp(message, "Black resign", 12) == 0) {
7949         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7950         return;
7951     } else if (strncmp(message, "White matches", 13) == 0 ||
7952                strncmp(message, "Black matches", 13) == 0   ) {
7953         /* [HGM] ignore GNUShogi noises */
7954         return;
7955     } else if (strncmp(message, "White", 5) == 0 &&
7956                message[5] != '(' &&
7957                StrStr(message, "Black") == NULL) {
7958         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7959         return;
7960     } else if (strncmp(message, "Black", 5) == 0 &&
7961                message[5] != '(') {
7962         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7963         return;
7964     } else if (strcmp(message, "resign") == 0 ||
7965                strcmp(message, "computer resigns") == 0) {
7966         switch (gameMode) {
7967           case MachinePlaysBlack:
7968           case IcsPlayingBlack:
7969             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7970             break;
7971           case MachinePlaysWhite:
7972           case IcsPlayingWhite:
7973             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7974             break;
7975           case TwoMachinesPlay:
7976             if (cps->twoMachinesColor[0] == 'w')
7977               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7978             else
7979               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7980             break;
7981           default:
7982             /* can't happen */
7983             break;
7984         }
7985         return;
7986     } else if (strncmp(message, "opponent mates", 14) == 0) {
7987         switch (gameMode) {
7988           case MachinePlaysBlack:
7989           case IcsPlayingBlack:
7990             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7991             break;
7992           case MachinePlaysWhite:
7993           case IcsPlayingWhite:
7994             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7995             break;
7996           case TwoMachinesPlay:
7997             if (cps->twoMachinesColor[0] == 'w')
7998               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7999             else
8000               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8001             break;
8002           default:
8003             /* can't happen */
8004             break;
8005         }
8006         return;
8007     } else if (strncmp(message, "computer mates", 14) == 0) {
8008         switch (gameMode) {
8009           case MachinePlaysBlack:
8010           case IcsPlayingBlack:
8011             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8012             break;
8013           case MachinePlaysWhite:
8014           case IcsPlayingWhite:
8015             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8016             break;
8017           case TwoMachinesPlay:
8018             if (cps->twoMachinesColor[0] == 'w')
8019               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8020             else
8021               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8022             break;
8023           default:
8024             /* can't happen */
8025             break;
8026         }
8027         return;
8028     } else if (strncmp(message, "checkmate", 9) == 0) {
8029         if (WhiteOnMove(forwardMostMove)) {
8030             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8031         } else {
8032             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8033         }
8034         return;
8035     } else if (strstr(message, "Draw") != NULL ||
8036                strstr(message, "game is a draw") != NULL) {
8037         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8038         return;
8039     } else if (strstr(message, "offer") != NULL &&
8040                strstr(message, "draw") != NULL) {
8041 #if ZIPPY
8042         if (appData.zippyPlay && first.initDone) {
8043             /* Relay offer to ICS */
8044             SendToICS(ics_prefix);
8045             SendToICS("draw\n");
8046         }
8047 #endif
8048         cps->offeredDraw = 2; /* valid until this engine moves twice */
8049         if (gameMode == TwoMachinesPlay) {
8050             if (cps->other->offeredDraw) {
8051                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8052             /* [HGM] in two-machine mode we delay relaying draw offer      */
8053             /* until after we also have move, to see if it is really claim */
8054             }
8055         } else if (gameMode == MachinePlaysWhite ||
8056                    gameMode == MachinePlaysBlack) {
8057           if (userOfferedDraw) {
8058             DisplayInformation(_("Machine accepts your draw offer"));
8059             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8060           } else {
8061             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8062           }
8063         }
8064     }
8065
8066
8067     /*
8068      * Look for thinking output
8069      */
8070     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8071           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8072                                 ) {
8073         int plylev, mvleft, mvtot, curscore, time;
8074         char mvname[MOVE_LEN];
8075         u64 nodes; // [DM]
8076         char plyext;
8077         int ignore = FALSE;
8078         int prefixHint = FALSE;
8079         mvname[0] = NULLCHAR;
8080
8081         switch (gameMode) {
8082           case MachinePlaysBlack:
8083           case IcsPlayingBlack:
8084             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8085             break;
8086           case MachinePlaysWhite:
8087           case IcsPlayingWhite:
8088             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8089             break;
8090           case AnalyzeMode:
8091           case AnalyzeFile:
8092             break;
8093           case IcsObserving: /* [DM] icsEngineAnalyze */
8094             if (!appData.icsEngineAnalyze) ignore = TRUE;
8095             break;
8096           case TwoMachinesPlay:
8097             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8098                 ignore = TRUE;
8099             }
8100             break;
8101           default:
8102             ignore = TRUE;
8103             break;
8104         }
8105
8106         if (!ignore) {
8107             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8108             buf1[0] = NULLCHAR;
8109             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8110                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8111
8112                 if (plyext != ' ' && plyext != '\t') {
8113                     time *= 100;
8114                 }
8115
8116                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8117                 if( cps->scoreIsAbsolute &&
8118                     ( gameMode == MachinePlaysBlack ||
8119                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8120                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8121                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8122                      !WhiteOnMove(currentMove)
8123                     ) )
8124                 {
8125                     curscore = -curscore;
8126                 }
8127
8128
8129                 tempStats.depth = plylev;
8130                 tempStats.nodes = nodes;
8131                 tempStats.time = time;
8132                 tempStats.score = curscore;
8133                 tempStats.got_only_move = 0;
8134
8135                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8136                         int ticklen;
8137
8138                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8139                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8140                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8141                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8142                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8143                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8144                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8145                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8146                 }
8147
8148                 /* Buffer overflow protection */
8149                 if (buf1[0] != NULLCHAR) {
8150                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8151                         && appData.debugMode) {
8152                         fprintf(debugFP,
8153                                 "PV is too long; using the first %u bytes.\n",
8154                                 (unsigned) sizeof(tempStats.movelist) - 1);
8155                     }
8156
8157                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8158                 } else {
8159                     sprintf(tempStats.movelist, " no PV\n");
8160                 }
8161
8162                 if (tempStats.seen_stat) {
8163                     tempStats.ok_to_send = 1;
8164                 }
8165
8166                 if (strchr(tempStats.movelist, '(') != NULL) {
8167                     tempStats.line_is_book = 1;
8168                     tempStats.nr_moves = 0;
8169                     tempStats.moves_left = 0;
8170                 } else {
8171                     tempStats.line_is_book = 0;
8172                 }
8173
8174                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8175                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8176
8177                 SendProgramStatsToFrontend( cps, &tempStats );
8178
8179                 /*
8180                     [AS] Protect the thinkOutput buffer from overflow... this
8181                     is only useful if buf1 hasn't overflowed first!
8182                 */
8183                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8184                          plylev,
8185                          (gameMode == TwoMachinesPlay ?
8186                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8187                          ((double) curscore) / 100.0,
8188                          prefixHint ? lastHint : "",
8189                          prefixHint ? " " : "" );
8190
8191                 if( buf1[0] != NULLCHAR ) {
8192                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8193
8194                     if( strlen(buf1) > max_len ) {
8195                         if( appData.debugMode) {
8196                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8197                         }
8198                         buf1[max_len+1] = '\0';
8199                     }
8200
8201                     strcat( thinkOutput, buf1 );
8202                 }
8203
8204                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8205                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8206                     DisplayMove(currentMove - 1);
8207                 }
8208                 return;
8209
8210             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8211                 /* crafty (9.25+) says "(only move) <move>"
8212                  * if there is only 1 legal move
8213                  */
8214                 sscanf(p, "(only move) %s", buf1);
8215                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8216                 sprintf(programStats.movelist, "%s (only move)", buf1);
8217                 programStats.depth = 1;
8218                 programStats.nr_moves = 1;
8219                 programStats.moves_left = 1;
8220                 programStats.nodes = 1;
8221                 programStats.time = 1;
8222                 programStats.got_only_move = 1;
8223
8224                 /* Not really, but we also use this member to
8225                    mean "line isn't going to change" (Crafty
8226                    isn't searching, so stats won't change) */
8227                 programStats.line_is_book = 1;
8228
8229                 SendProgramStatsToFrontend( cps, &programStats );
8230
8231                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8232                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8233                     DisplayMove(currentMove - 1);
8234                 }
8235                 return;
8236             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8237                               &time, &nodes, &plylev, &mvleft,
8238                               &mvtot, mvname) >= 5) {
8239                 /* The stat01: line is from Crafty (9.29+) in response
8240                    to the "." command */
8241                 programStats.seen_stat = 1;
8242                 cps->maybeThinking = TRUE;
8243
8244                 if (programStats.got_only_move || !appData.periodicUpdates)
8245                   return;
8246
8247                 programStats.depth = plylev;
8248                 programStats.time = time;
8249                 programStats.nodes = nodes;
8250                 programStats.moves_left = mvleft;
8251                 programStats.nr_moves = mvtot;
8252                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8253                 programStats.ok_to_send = 1;
8254                 programStats.movelist[0] = '\0';
8255
8256                 SendProgramStatsToFrontend( cps, &programStats );
8257
8258                 return;
8259
8260             } else if (strncmp(message,"++",2) == 0) {
8261                 /* Crafty 9.29+ outputs this */
8262                 programStats.got_fail = 2;
8263                 return;
8264
8265             } else if (strncmp(message,"--",2) == 0) {
8266                 /* Crafty 9.29+ outputs this */
8267                 programStats.got_fail = 1;
8268                 return;
8269
8270             } else if (thinkOutput[0] != NULLCHAR &&
8271                        strncmp(message, "    ", 4) == 0) {
8272                 unsigned message_len;
8273
8274                 p = message;
8275                 while (*p && *p == ' ') p++;
8276
8277                 message_len = strlen( p );
8278
8279                 /* [AS] Avoid buffer overflow */
8280                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8281                     strcat(thinkOutput, " ");
8282                     strcat(thinkOutput, p);
8283                 }
8284
8285                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8286                     strcat(programStats.movelist, " ");
8287                     strcat(programStats.movelist, p);
8288                 }
8289
8290                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8291                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8292                     DisplayMove(currentMove - 1);
8293                 }
8294                 return;
8295             }
8296         }
8297         else {
8298             buf1[0] = NULLCHAR;
8299
8300             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8301                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8302             {
8303                 ChessProgramStats cpstats;
8304
8305                 if (plyext != ' ' && plyext != '\t') {
8306                     time *= 100;
8307                 }
8308
8309                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8310                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8311                     curscore = -curscore;
8312                 }
8313
8314                 cpstats.depth = plylev;
8315                 cpstats.nodes = nodes;
8316                 cpstats.time = time;
8317                 cpstats.score = curscore;
8318                 cpstats.got_only_move = 0;
8319                 cpstats.movelist[0] = '\0';
8320
8321                 if (buf1[0] != NULLCHAR) {
8322                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8323                 }
8324
8325                 cpstats.ok_to_send = 0;
8326                 cpstats.line_is_book = 0;
8327                 cpstats.nr_moves = 0;
8328                 cpstats.moves_left = 0;
8329
8330                 SendProgramStatsToFrontend( cps, &cpstats );
8331             }
8332         }
8333     }
8334 }
8335
8336
8337 /* Parse a game score from the character string "game", and
8338    record it as the history of the current game.  The game
8339    score is NOT assumed to start from the standard position.
8340    The display is not updated in any way.
8341    */
8342 void
8343 ParseGameHistory(game)
8344      char *game;
8345 {
8346     ChessMove moveType;
8347     int fromX, fromY, toX, toY, boardIndex;
8348     char promoChar;
8349     char *p, *q;
8350     char buf[MSG_SIZ];
8351
8352     if (appData.debugMode)
8353       fprintf(debugFP, "Parsing game history: %s\n", game);
8354
8355     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8356     gameInfo.site = StrSave(appData.icsHost);
8357     gameInfo.date = PGNDate();
8358     gameInfo.round = StrSave("-");
8359
8360     /* Parse out names of players */
8361     while (*game == ' ') game++;
8362     p = buf;
8363     while (*game != ' ') *p++ = *game++;
8364     *p = NULLCHAR;
8365     gameInfo.white = StrSave(buf);
8366     while (*game == ' ') game++;
8367     p = buf;
8368     while (*game != ' ' && *game != '\n') *p++ = *game++;
8369     *p = NULLCHAR;
8370     gameInfo.black = StrSave(buf);
8371
8372     /* Parse moves */
8373     boardIndex = blackPlaysFirst ? 1 : 0;
8374     yynewstr(game);
8375     for (;;) {
8376         yyboardindex = boardIndex;
8377         moveType = (ChessMove) Myylex();
8378         switch (moveType) {
8379           case IllegalMove:             /* maybe suicide chess, etc. */
8380   if (appData.debugMode) {
8381     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8382     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8383     setbuf(debugFP, NULL);
8384   }
8385           case WhitePromotion:
8386           case BlackPromotion:
8387           case WhiteNonPromotion:
8388           case BlackNonPromotion:
8389           case NormalMove:
8390           case WhiteCapturesEnPassant:
8391           case BlackCapturesEnPassant:
8392           case WhiteKingSideCastle:
8393           case WhiteQueenSideCastle:
8394           case BlackKingSideCastle:
8395           case BlackQueenSideCastle:
8396           case WhiteKingSideCastleWild:
8397           case WhiteQueenSideCastleWild:
8398           case BlackKingSideCastleWild:
8399           case BlackQueenSideCastleWild:
8400           /* PUSH Fabien */
8401           case WhiteHSideCastleFR:
8402           case WhiteASideCastleFR:
8403           case BlackHSideCastleFR:
8404           case BlackASideCastleFR:
8405           /* POP Fabien */
8406             fromX = currentMoveString[0] - AAA;
8407             fromY = currentMoveString[1] - ONE;
8408             toX = currentMoveString[2] - AAA;
8409             toY = currentMoveString[3] - ONE;
8410             promoChar = currentMoveString[4];
8411             break;
8412           case WhiteDrop:
8413           case BlackDrop:
8414             fromX = moveType == WhiteDrop ?
8415               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8416             (int) CharToPiece(ToLower(currentMoveString[0]));
8417             fromY = DROP_RANK;
8418             toX = currentMoveString[2] - AAA;
8419             toY = currentMoveString[3] - ONE;
8420             promoChar = NULLCHAR;
8421             break;
8422           case AmbiguousMove:
8423             /* bug? */
8424             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8425   if (appData.debugMode) {
8426     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8427     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8428     setbuf(debugFP, NULL);
8429   }
8430             DisplayError(buf, 0);
8431             return;
8432           case ImpossibleMove:
8433             /* bug? */
8434             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8435   if (appData.debugMode) {
8436     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8437     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8438     setbuf(debugFP, NULL);
8439   }
8440             DisplayError(buf, 0);
8441             return;
8442           case EndOfFile:
8443             if (boardIndex < backwardMostMove) {
8444                 /* Oops, gap.  How did that happen? */
8445                 DisplayError(_("Gap in move list"), 0);
8446                 return;
8447             }
8448             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8449             if (boardIndex > forwardMostMove) {
8450                 forwardMostMove = boardIndex;
8451             }
8452             return;
8453           case ElapsedTime:
8454             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8455                 strcat(parseList[boardIndex-1], " ");
8456                 strcat(parseList[boardIndex-1], yy_text);
8457             }
8458             continue;
8459           case Comment:
8460           case PGNTag:
8461           case NAG:
8462           default:
8463             /* ignore */
8464             continue;
8465           case WhiteWins:
8466           case BlackWins:
8467           case GameIsDrawn:
8468           case GameUnfinished:
8469             if (gameMode == IcsExamining) {
8470                 if (boardIndex < backwardMostMove) {
8471                     /* Oops, gap.  How did that happen? */
8472                     return;
8473                 }
8474                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8475                 return;
8476             }
8477             gameInfo.result = moveType;
8478             p = strchr(yy_text, '{');
8479             if (p == NULL) p = strchr(yy_text, '(');
8480             if (p == NULL) {
8481                 p = yy_text;
8482                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8483             } else {
8484                 q = strchr(p, *p == '{' ? '}' : ')');
8485                 if (q != NULL) *q = NULLCHAR;
8486                 p++;
8487             }
8488             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8489             gameInfo.resultDetails = StrSave(p);
8490             continue;
8491         }
8492         if (boardIndex >= forwardMostMove &&
8493             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8494             backwardMostMove = blackPlaysFirst ? 1 : 0;
8495             return;
8496         }
8497         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8498                                  fromY, fromX, toY, toX, promoChar,
8499                                  parseList[boardIndex]);
8500         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8501         /* currentMoveString is set as a side-effect of yylex */
8502         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8503         strcat(moveList[boardIndex], "\n");
8504         boardIndex++;
8505         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8506         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8507           case MT_NONE:
8508           case MT_STALEMATE:
8509           default:
8510             break;
8511           case MT_CHECK:
8512             if(gameInfo.variant != VariantShogi)
8513                 strcat(parseList[boardIndex - 1], "+");
8514             break;
8515           case MT_CHECKMATE:
8516           case MT_STAINMATE:
8517             strcat(parseList[boardIndex - 1], "#");
8518             break;
8519         }
8520     }
8521 }
8522
8523
8524 /* Apply a move to the given board  */
8525 void
8526 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8527      int fromX, fromY, toX, toY;
8528      int promoChar;
8529      Board board;
8530 {
8531   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8532   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8533
8534     /* [HGM] compute & store e.p. status and castling rights for new position */
8535     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8536
8537       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8538       oldEP = (signed char)board[EP_STATUS];
8539       board[EP_STATUS] = EP_NONE;
8540
8541       if( board[toY][toX] != EmptySquare )
8542            board[EP_STATUS] = EP_CAPTURE;
8543
8544   if (fromY == DROP_RANK) {
8545         /* must be first */
8546         piece = board[toY][toX] = (ChessSquare) fromX;
8547   } else {
8548       int i;
8549
8550       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8551            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8552                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8553       } else
8554       if( board[fromY][fromX] == WhitePawn ) {
8555            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8556                board[EP_STATUS] = EP_PAWN_MOVE;
8557            if( toY-fromY==2) {
8558                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8559                         gameInfo.variant != VariantBerolina || toX < fromX)
8560                       board[EP_STATUS] = toX | berolina;
8561                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8562                         gameInfo.variant != VariantBerolina || toX > fromX)
8563                       board[EP_STATUS] = toX;
8564            }
8565       } else
8566       if( board[fromY][fromX] == BlackPawn ) {
8567            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8568                board[EP_STATUS] = EP_PAWN_MOVE;
8569            if( toY-fromY== -2) {
8570                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8571                         gameInfo.variant != VariantBerolina || toX < fromX)
8572                       board[EP_STATUS] = toX | berolina;
8573                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8574                         gameInfo.variant != VariantBerolina || toX > fromX)
8575                       board[EP_STATUS] = toX;
8576            }
8577        }
8578
8579        for(i=0; i<nrCastlingRights; i++) {
8580            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8581               board[CASTLING][i] == toX   && castlingRank[i] == toY
8582              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8583        }
8584
8585      if (fromX == toX && fromY == toY) return;
8586
8587      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8588      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8589      if(gameInfo.variant == VariantKnightmate)
8590          king += (int) WhiteUnicorn - (int) WhiteKing;
8591
8592     /* Code added by Tord: */
8593     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8594     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8595         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8596       board[fromY][fromX] = EmptySquare;
8597       board[toY][toX] = EmptySquare;
8598       if((toX > fromX) != (piece == WhiteRook)) {
8599         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8600       } else {
8601         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8602       }
8603     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8604                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8605       board[fromY][fromX] = EmptySquare;
8606       board[toY][toX] = EmptySquare;
8607       if((toX > fromX) != (piece == BlackRook)) {
8608         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8609       } else {
8610         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8611       }
8612     /* End of code added by Tord */
8613
8614     } else if (board[fromY][fromX] == king
8615         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8616         && toY == fromY && toX > fromX+1) {
8617         board[fromY][fromX] = EmptySquare;
8618         board[toY][toX] = king;
8619         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8620         board[fromY][BOARD_RGHT-1] = EmptySquare;
8621     } else if (board[fromY][fromX] == king
8622         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8623                && toY == fromY && toX < fromX-1) {
8624         board[fromY][fromX] = EmptySquare;
8625         board[toY][toX] = king;
8626         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8627         board[fromY][BOARD_LEFT] = EmptySquare;
8628     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8629                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8630                && toY >= BOARD_HEIGHT-promoRank
8631                ) {
8632         /* white pawn promotion */
8633         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8634         if (board[toY][toX] == EmptySquare) {
8635             board[toY][toX] = WhiteQueen;
8636         }
8637         if(gameInfo.variant==VariantBughouse ||
8638            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8639             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8640         board[fromY][fromX] = EmptySquare;
8641     } else if ((fromY == BOARD_HEIGHT-4)
8642                && (toX != fromX)
8643                && gameInfo.variant != VariantXiangqi
8644                && gameInfo.variant != VariantBerolina
8645                && (board[fromY][fromX] == WhitePawn)
8646                && (board[toY][toX] == EmptySquare)) {
8647         board[fromY][fromX] = EmptySquare;
8648         board[toY][toX] = WhitePawn;
8649         captured = board[toY - 1][toX];
8650         board[toY - 1][toX] = EmptySquare;
8651     } else if ((fromY == BOARD_HEIGHT-4)
8652                && (toX == fromX)
8653                && gameInfo.variant == VariantBerolina
8654                && (board[fromY][fromX] == WhitePawn)
8655                && (board[toY][toX] == EmptySquare)) {
8656         board[fromY][fromX] = EmptySquare;
8657         board[toY][toX] = WhitePawn;
8658         if(oldEP & EP_BEROLIN_A) {
8659                 captured = board[fromY][fromX-1];
8660                 board[fromY][fromX-1] = EmptySquare;
8661         }else{  captured = board[fromY][fromX+1];
8662                 board[fromY][fromX+1] = EmptySquare;
8663         }
8664     } else if (board[fromY][fromX] == king
8665         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8666                && toY == fromY && toX > fromX+1) {
8667         board[fromY][fromX] = EmptySquare;
8668         board[toY][toX] = king;
8669         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8670         board[fromY][BOARD_RGHT-1] = EmptySquare;
8671     } else if (board[fromY][fromX] == king
8672         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8673                && toY == fromY && toX < fromX-1) {
8674         board[fromY][fromX] = EmptySquare;
8675         board[toY][toX] = king;
8676         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8677         board[fromY][BOARD_LEFT] = EmptySquare;
8678     } else if (fromY == 7 && fromX == 3
8679                && board[fromY][fromX] == BlackKing
8680                && toY == 7 && toX == 5) {
8681         board[fromY][fromX] = EmptySquare;
8682         board[toY][toX] = BlackKing;
8683         board[fromY][7] = EmptySquare;
8684         board[toY][4] = BlackRook;
8685     } else if (fromY == 7 && fromX == 3
8686                && board[fromY][fromX] == BlackKing
8687                && toY == 7 && toX == 1) {
8688         board[fromY][fromX] = EmptySquare;
8689         board[toY][toX] = BlackKing;
8690         board[fromY][0] = EmptySquare;
8691         board[toY][2] = BlackRook;
8692     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8693                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8694                && toY < promoRank
8695                ) {
8696         /* black pawn promotion */
8697         board[toY][toX] = CharToPiece(ToLower(promoChar));
8698         if (board[toY][toX] == EmptySquare) {
8699             board[toY][toX] = BlackQueen;
8700         }
8701         if(gameInfo.variant==VariantBughouse ||
8702            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8703             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8704         board[fromY][fromX] = EmptySquare;
8705     } else if ((fromY == 3)
8706                && (toX != fromX)
8707                && gameInfo.variant != VariantXiangqi
8708                && gameInfo.variant != VariantBerolina
8709                && (board[fromY][fromX] == BlackPawn)
8710                && (board[toY][toX] == EmptySquare)) {
8711         board[fromY][fromX] = EmptySquare;
8712         board[toY][toX] = BlackPawn;
8713         captured = board[toY + 1][toX];
8714         board[toY + 1][toX] = EmptySquare;
8715     } else if ((fromY == 3)
8716                && (toX == fromX)
8717                && gameInfo.variant == VariantBerolina
8718                && (board[fromY][fromX] == BlackPawn)
8719                && (board[toY][toX] == EmptySquare)) {
8720         board[fromY][fromX] = EmptySquare;
8721         board[toY][toX] = BlackPawn;
8722         if(oldEP & EP_BEROLIN_A) {
8723                 captured = board[fromY][fromX-1];
8724                 board[fromY][fromX-1] = EmptySquare;
8725         }else{  captured = board[fromY][fromX+1];
8726                 board[fromY][fromX+1] = EmptySquare;
8727         }
8728     } else {
8729         board[toY][toX] = board[fromY][fromX];
8730         board[fromY][fromX] = EmptySquare;
8731     }
8732   }
8733
8734     if (gameInfo.holdingsWidth != 0) {
8735
8736       /* !!A lot more code needs to be written to support holdings  */
8737       /* [HGM] OK, so I have written it. Holdings are stored in the */
8738       /* penultimate board files, so they are automaticlly stored   */
8739       /* in the game history.                                       */
8740       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8741                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8742         /* Delete from holdings, by decreasing count */
8743         /* and erasing image if necessary            */
8744         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8745         if(p < (int) BlackPawn) { /* white drop */
8746              p -= (int)WhitePawn;
8747                  p = PieceToNumber((ChessSquare)p);
8748              if(p >= gameInfo.holdingsSize) p = 0;
8749              if(--board[p][BOARD_WIDTH-2] <= 0)
8750                   board[p][BOARD_WIDTH-1] = EmptySquare;
8751              if((int)board[p][BOARD_WIDTH-2] < 0)
8752                         board[p][BOARD_WIDTH-2] = 0;
8753         } else {                  /* black drop */
8754              p -= (int)BlackPawn;
8755                  p = PieceToNumber((ChessSquare)p);
8756              if(p >= gameInfo.holdingsSize) p = 0;
8757              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8758                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8759              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8760                         board[BOARD_HEIGHT-1-p][1] = 0;
8761         }
8762       }
8763       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8764           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8765         /* [HGM] holdings: Add to holdings, if holdings exist */
8766         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8767                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8768                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8769         }
8770         p = (int) captured;
8771         if (p >= (int) BlackPawn) {
8772           p -= (int)BlackPawn;
8773           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8774                   /* in Shogi restore piece to its original  first */
8775                   captured = (ChessSquare) (DEMOTED captured);
8776                   p = DEMOTED p;
8777           }
8778           p = PieceToNumber((ChessSquare)p);
8779           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8780           board[p][BOARD_WIDTH-2]++;
8781           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8782         } else {
8783           p -= (int)WhitePawn;
8784           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8785                   captured = (ChessSquare) (DEMOTED captured);
8786                   p = DEMOTED p;
8787           }
8788           p = PieceToNumber((ChessSquare)p);
8789           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8790           board[BOARD_HEIGHT-1-p][1]++;
8791           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8792         }
8793       }
8794     } else if (gameInfo.variant == VariantAtomic) {
8795       if (captured != EmptySquare) {
8796         int y, x;
8797         for (y = toY-1; y <= toY+1; y++) {
8798           for (x = toX-1; x <= toX+1; x++) {
8799             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8800                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8801               board[y][x] = EmptySquare;
8802             }
8803           }
8804         }
8805         board[toY][toX] = EmptySquare;
8806       }
8807     }
8808     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8809         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8810     } else
8811     if(promoChar == '+') {
8812         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8813         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8814     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8815         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8816     }
8817     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8818                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8819         // [HGM] superchess: take promotion piece out of holdings
8820         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8821         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8822             if(!--board[k][BOARD_WIDTH-2])
8823                 board[k][BOARD_WIDTH-1] = EmptySquare;
8824         } else {
8825             if(!--board[BOARD_HEIGHT-1-k][1])
8826                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8827         }
8828     }
8829
8830 }
8831
8832 /* Updates forwardMostMove */
8833 void
8834 MakeMove(fromX, fromY, toX, toY, promoChar)
8835      int fromX, fromY, toX, toY;
8836      int promoChar;
8837 {
8838 //    forwardMostMove++; // [HGM] bare: moved downstream
8839
8840     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8841         int timeLeft; static int lastLoadFlag=0; int king, piece;
8842         piece = boards[forwardMostMove][fromY][fromX];
8843         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8844         if(gameInfo.variant == VariantKnightmate)
8845             king += (int) WhiteUnicorn - (int) WhiteKing;
8846         if(forwardMostMove == 0) {
8847             if(blackPlaysFirst)
8848                 fprintf(serverMoves, "%s;", second.tidy);
8849             fprintf(serverMoves, "%s;", first.tidy);
8850             if(!blackPlaysFirst)
8851                 fprintf(serverMoves, "%s;", second.tidy);
8852         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8853         lastLoadFlag = loadFlag;
8854         // print base move
8855         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8856         // print castling suffix
8857         if( toY == fromY && piece == king ) {
8858             if(toX-fromX > 1)
8859                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8860             if(fromX-toX >1)
8861                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8862         }
8863         // e.p. suffix
8864         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8865              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8866              boards[forwardMostMove][toY][toX] == EmptySquare
8867              && fromX != toX && fromY != toY)
8868                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8869         // promotion suffix
8870         if(promoChar != NULLCHAR)
8871                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8872         if(!loadFlag) {
8873             fprintf(serverMoves, "/%d/%d",
8874                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8875             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8876             else                      timeLeft = blackTimeRemaining/1000;
8877             fprintf(serverMoves, "/%d", timeLeft);
8878         }
8879         fflush(serverMoves);
8880     }
8881
8882     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8883       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8884                         0, 1);
8885       return;
8886     }
8887     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8888     if (commentList[forwardMostMove+1] != NULL) {
8889         free(commentList[forwardMostMove+1]);
8890         commentList[forwardMostMove+1] = NULL;
8891     }
8892     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8893     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8894     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8895     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8896     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8897     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8898     gameInfo.result = GameUnfinished;
8899     if (gameInfo.resultDetails != NULL) {
8900         free(gameInfo.resultDetails);
8901         gameInfo.resultDetails = NULL;
8902     }
8903     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8904                               moveList[forwardMostMove - 1]);
8905     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8906                              PosFlags(forwardMostMove - 1),
8907                              fromY, fromX, toY, toX, promoChar,
8908                              parseList[forwardMostMove - 1]);
8909     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8910       case MT_NONE:
8911       case MT_STALEMATE:
8912       default:
8913         break;
8914       case MT_CHECK:
8915         if(gameInfo.variant != VariantShogi)
8916             strcat(parseList[forwardMostMove - 1], "+");
8917         break;
8918       case MT_CHECKMATE:
8919       case MT_STAINMATE:
8920         strcat(parseList[forwardMostMove - 1], "#");
8921         break;
8922     }
8923     if (appData.debugMode) {
8924         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8925     }
8926
8927 }
8928
8929 /* Updates currentMove if not pausing */
8930 void
8931 ShowMove(fromX, fromY, toX, toY)
8932 {
8933     int instant = (gameMode == PlayFromGameFile) ?
8934         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8935     if(appData.noGUI) return;
8936     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8937         if (!instant) {
8938             if (forwardMostMove == currentMove + 1) {
8939                 AnimateMove(boards[forwardMostMove - 1],
8940                             fromX, fromY, toX, toY);
8941             }
8942             if (appData.highlightLastMove) {
8943                 SetHighlights(fromX, fromY, toX, toY);
8944             }
8945         }
8946         currentMove = forwardMostMove;
8947     }
8948
8949     if (instant) return;
8950
8951     DisplayMove(currentMove - 1);
8952     DrawPosition(FALSE, boards[currentMove]);
8953     DisplayBothClocks();
8954     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8955 }
8956
8957 void SendEgtPath(ChessProgramState *cps)
8958 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8959         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8960
8961         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8962
8963         while(*p) {
8964             char c, *q = name+1, *r, *s;
8965
8966             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8967             while(*p && *p != ',') *q++ = *p++;
8968             *q++ = ':'; *q = 0;
8969             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8970                 strcmp(name, ",nalimov:") == 0 ) {
8971                 // take nalimov path from the menu-changeable option first, if it is defined
8972               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8973                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8974             } else
8975             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8976                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8977                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8978                 s = r = StrStr(s, ":") + 1; // beginning of path info
8979                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8980                 c = *r; *r = 0;             // temporarily null-terminate path info
8981                     *--q = 0;               // strip of trailig ':' from name
8982                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8983                 *r = c;
8984                 SendToProgram(buf,cps);     // send egtbpath command for this format
8985             }
8986             if(*p == ',') p++; // read away comma to position for next format name
8987         }
8988 }
8989
8990 void
8991 InitChessProgram(cps, setup)
8992      ChessProgramState *cps;
8993      int setup; /* [HGM] needed to setup FRC opening position */
8994 {
8995     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8996     if (appData.noChessProgram) return;
8997     hintRequested = FALSE;
8998     bookRequested = FALSE;
8999
9000     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9001     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9002     if(cps->memSize) { /* [HGM] memory */
9003       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9004         SendToProgram(buf, cps);
9005     }
9006     SendEgtPath(cps); /* [HGM] EGT */
9007     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9008       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9009         SendToProgram(buf, cps);
9010     }
9011
9012     SendToProgram(cps->initString, cps);
9013     if (gameInfo.variant != VariantNormal &&
9014         gameInfo.variant != VariantLoadable
9015         /* [HGM] also send variant if board size non-standard */
9016         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9017                                             ) {
9018       char *v = VariantName(gameInfo.variant);
9019       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9020         /* [HGM] in protocol 1 we have to assume all variants valid */
9021         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9022         DisplayFatalError(buf, 0, 1);
9023         return;
9024       }
9025
9026       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9027       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9028       if( gameInfo.variant == VariantXiangqi )
9029            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9030       if( gameInfo.variant == VariantShogi )
9031            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9032       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9033            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9034       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9035           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9036            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9037       if( gameInfo.variant == VariantCourier )
9038            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9039       if( gameInfo.variant == VariantSuper )
9040            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9041       if( gameInfo.variant == VariantGreat )
9042            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9043       if( gameInfo.variant == VariantSChess )
9044            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9045
9046       if(overruled) {
9047         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9048                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9049            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9050            if(StrStr(cps->variants, b) == NULL) {
9051                // specific sized variant not known, check if general sizing allowed
9052                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9053                    if(StrStr(cps->variants, "boardsize") == NULL) {
9054                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9055                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9056                        DisplayFatalError(buf, 0, 1);
9057                        return;
9058                    }
9059                    /* [HGM] here we really should compare with the maximum supported board size */
9060                }
9061            }
9062       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9063       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9064       SendToProgram(buf, cps);
9065     }
9066     currentlyInitializedVariant = gameInfo.variant;
9067
9068     /* [HGM] send opening position in FRC to first engine */
9069     if(setup) {
9070           SendToProgram("force\n", cps);
9071           SendBoard(cps, 0);
9072           /* engine is now in force mode! Set flag to wake it up after first move. */
9073           setboardSpoiledMachineBlack = 1;
9074     }
9075
9076     if (cps->sendICS) {
9077       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9078       SendToProgram(buf, cps);
9079     }
9080     cps->maybeThinking = FALSE;
9081     cps->offeredDraw = 0;
9082     if (!appData.icsActive) {
9083         SendTimeControl(cps, movesPerSession, timeControl,
9084                         timeIncrement, appData.searchDepth,
9085                         searchTime);
9086     }
9087     if (appData.showThinking
9088         // [HGM] thinking: four options require thinking output to be sent
9089         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9090                                 ) {
9091         SendToProgram("post\n", cps);
9092     }
9093     SendToProgram("hard\n", cps);
9094     if (!appData.ponderNextMove) {
9095         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9096            it without being sure what state we are in first.  "hard"
9097            is not a toggle, so that one is OK.
9098          */
9099         SendToProgram("easy\n", cps);
9100     }
9101     if (cps->usePing) {
9102       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9103       SendToProgram(buf, cps);
9104     }
9105     cps->initDone = TRUE;
9106 }
9107
9108
9109 void
9110 StartChessProgram(cps)
9111      ChessProgramState *cps;
9112 {
9113     char buf[MSG_SIZ];
9114     int err;
9115
9116     if (appData.noChessProgram) return;
9117     cps->initDone = FALSE;
9118
9119     if (strcmp(cps->host, "localhost") == 0) {
9120         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9121     } else if (*appData.remoteShell == NULLCHAR) {
9122         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9123     } else {
9124         if (*appData.remoteUser == NULLCHAR) {
9125           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9126                     cps->program);
9127         } else {
9128           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9129                     cps->host, appData.remoteUser, cps->program);
9130         }
9131         err = StartChildProcess(buf, "", &cps->pr);
9132     }
9133
9134     if (err != 0) {
9135       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9136         DisplayFatalError(buf, err, 1);
9137         cps->pr = NoProc;
9138         cps->isr = NULL;
9139         return;
9140     }
9141
9142     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9143     if (cps->protocolVersion > 1) {
9144       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9145       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9146       cps->comboCnt = 0;  //                and values of combo boxes
9147       SendToProgram(buf, cps);
9148     } else {
9149       SendToProgram("xboard\n", cps);
9150     }
9151 }
9152
9153
9154 void
9155 TwoMachinesEventIfReady P((void))
9156 {
9157   if (first.lastPing != first.lastPong) {
9158     DisplayMessage("", _("Waiting for first chess program"));
9159     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9160     return;
9161   }
9162   if (second.lastPing != second.lastPong) {
9163     DisplayMessage("", _("Waiting for second chess program"));
9164     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9165     return;
9166   }
9167   ThawUI();
9168   TwoMachinesEvent();
9169 }
9170
9171 void
9172 NextMatchGame P((void))
9173 {
9174     int index; /* [HGM] autoinc: step load index during match */
9175     Reset(FALSE, TRUE);
9176     if (*appData.loadGameFile != NULLCHAR) {
9177         index = appData.loadGameIndex;
9178         if(index < 0) { // [HGM] autoinc
9179             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9180             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9181         }
9182         LoadGameFromFile(appData.loadGameFile,
9183                          index,
9184                          appData.loadGameFile, FALSE);
9185     } else if (*appData.loadPositionFile != NULLCHAR) {
9186         index = appData.loadPositionIndex;
9187         if(index < 0) { // [HGM] autoinc
9188             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9189             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9190         }
9191         LoadPositionFromFile(appData.loadPositionFile,
9192                              index,
9193                              appData.loadPositionFile);
9194     }
9195     TwoMachinesEventIfReady();
9196 }
9197
9198 void UserAdjudicationEvent( int result )
9199 {
9200     ChessMove gameResult = GameIsDrawn;
9201
9202     if( result > 0 ) {
9203         gameResult = WhiteWins;
9204     }
9205     else if( result < 0 ) {
9206         gameResult = BlackWins;
9207     }
9208
9209     if( gameMode == TwoMachinesPlay ) {
9210         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9211     }
9212 }
9213
9214
9215 // [HGM] save: calculate checksum of game to make games easily identifiable
9216 int StringCheckSum(char *s)
9217 {
9218         int i = 0;
9219         if(s==NULL) return 0;
9220         while(*s) i = i*259 + *s++;
9221         return i;
9222 }
9223
9224 int GameCheckSum()
9225 {
9226         int i, sum=0;
9227         for(i=backwardMostMove; i<forwardMostMove; i++) {
9228                 sum += pvInfoList[i].depth;
9229                 sum += StringCheckSum(parseList[i]);
9230                 sum += StringCheckSum(commentList[i]);
9231                 sum *= 261;
9232         }
9233         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9234         return sum + StringCheckSum(commentList[i]);
9235 } // end of save patch
9236
9237 void
9238 GameEnds(result, resultDetails, whosays)
9239      ChessMove result;
9240      char *resultDetails;
9241      int whosays;
9242 {
9243     GameMode nextGameMode;
9244     int isIcsGame;
9245     char buf[MSG_SIZ], popupRequested = 0;
9246
9247     if(endingGame) return; /* [HGM] crash: forbid recursion */
9248     endingGame = 1;
9249     if(twoBoards) { // [HGM] dual: switch back to one board
9250         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9251         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9252     }
9253     if (appData.debugMode) {
9254       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9255               result, resultDetails ? resultDetails : "(null)", whosays);
9256     }
9257
9258     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9259
9260     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9261         /* If we are playing on ICS, the server decides when the
9262            game is over, but the engine can offer to draw, claim
9263            a draw, or resign.
9264          */
9265 #if ZIPPY
9266         if (appData.zippyPlay && first.initDone) {
9267             if (result == GameIsDrawn) {
9268                 /* In case draw still needs to be claimed */
9269                 SendToICS(ics_prefix);
9270                 SendToICS("draw\n");
9271             } else if (StrCaseStr(resultDetails, "resign")) {
9272                 SendToICS(ics_prefix);
9273                 SendToICS("resign\n");
9274             }
9275         }
9276 #endif
9277         endingGame = 0; /* [HGM] crash */
9278         return;
9279     }
9280
9281     /* If we're loading the game from a file, stop */
9282     if (whosays == GE_FILE) {
9283       (void) StopLoadGameTimer();
9284       gameFileFP = NULL;
9285     }
9286
9287     /* Cancel draw offers */
9288     first.offeredDraw = second.offeredDraw = 0;
9289
9290     /* If this is an ICS game, only ICS can really say it's done;
9291        if not, anyone can. */
9292     isIcsGame = (gameMode == IcsPlayingWhite ||
9293                  gameMode == IcsPlayingBlack ||
9294                  gameMode == IcsObserving    ||
9295                  gameMode == IcsExamining);
9296
9297     if (!isIcsGame || whosays == GE_ICS) {
9298         /* OK -- not an ICS game, or ICS said it was done */
9299         StopClocks();
9300         if (!isIcsGame && !appData.noChessProgram)
9301           SetUserThinkingEnables();
9302
9303         /* [HGM] if a machine claims the game end we verify this claim */
9304         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9305             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9306                 char claimer;
9307                 ChessMove trueResult = (ChessMove) -1;
9308
9309                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9310                                             first.twoMachinesColor[0] :
9311                                             second.twoMachinesColor[0] ;
9312
9313                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9314                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9315                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9316                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9317                 } else
9318                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9319                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9320                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9321                 } else
9322                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9323                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9324                 }
9325
9326                 // now verify win claims, but not in drop games, as we don't understand those yet
9327                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9328                                                  || gameInfo.variant == VariantGreat) &&
9329                     (result == WhiteWins && claimer == 'w' ||
9330                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9331                       if (appData.debugMode) {
9332                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9333                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9334                       }
9335                       if(result != trueResult) {
9336                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9337                               result = claimer == 'w' ? BlackWins : WhiteWins;
9338                               resultDetails = buf;
9339                       }
9340                 } else
9341                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9342                     && (forwardMostMove <= backwardMostMove ||
9343                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9344                         (claimer=='b')==(forwardMostMove&1))
9345                                                                                   ) {
9346                       /* [HGM] verify: draws that were not flagged are false claims */
9347                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9348                       result = claimer == 'w' ? BlackWins : WhiteWins;
9349                       resultDetails = buf;
9350                 }
9351                 /* (Claiming a loss is accepted no questions asked!) */
9352             }
9353             /* [HGM] bare: don't allow bare King to win */
9354             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9355                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9356                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9357                && result != GameIsDrawn)
9358             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9359                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9360                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9361                         if(p >= 0 && p <= (int)WhiteKing) k++;
9362                 }
9363                 if (appData.debugMode) {
9364                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9365                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9366                 }
9367                 if(k <= 1) {
9368                         result = GameIsDrawn;
9369                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9370                         resultDetails = buf;
9371                 }
9372             }
9373         }
9374
9375
9376         if(serverMoves != NULL && !loadFlag) { char c = '=';
9377             if(result==WhiteWins) c = '+';
9378             if(result==BlackWins) c = '-';
9379             if(resultDetails != NULL)
9380                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9381         }
9382         if (resultDetails != NULL) {
9383             gameInfo.result = result;
9384             gameInfo.resultDetails = StrSave(resultDetails);
9385
9386             /* display last move only if game was not loaded from file */
9387             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9388                 DisplayMove(currentMove - 1);
9389
9390             if (forwardMostMove != 0) {
9391                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9392                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9393                                                                 ) {
9394                     if (*appData.saveGameFile != NULLCHAR) {
9395                         SaveGameToFile(appData.saveGameFile, TRUE);
9396                     } else if (appData.autoSaveGames) {
9397                         AutoSaveGame();
9398                     }
9399                     if (*appData.savePositionFile != NULLCHAR) {
9400                         SavePositionToFile(appData.savePositionFile);
9401                     }
9402                 }
9403             }
9404
9405             /* Tell program how game ended in case it is learning */
9406             /* [HGM] Moved this to after saving the PGN, just in case */
9407             /* engine died and we got here through time loss. In that */
9408             /* case we will get a fatal error writing the pipe, which */
9409             /* would otherwise lose us the PGN.                       */
9410             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9411             /* output during GameEnds should never be fatal anymore   */
9412             if (gameMode == MachinePlaysWhite ||
9413                 gameMode == MachinePlaysBlack ||
9414                 gameMode == TwoMachinesPlay ||
9415                 gameMode == IcsPlayingWhite ||
9416                 gameMode == IcsPlayingBlack ||
9417                 gameMode == BeginningOfGame) {
9418                 char buf[MSG_SIZ];
9419                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9420                         resultDetails);
9421                 if (first.pr != NoProc) {
9422                     SendToProgram(buf, &first);
9423                 }
9424                 if (second.pr != NoProc &&
9425                     gameMode == TwoMachinesPlay) {
9426                     SendToProgram(buf, &second);
9427                 }
9428             }
9429         }
9430
9431         if (appData.icsActive) {
9432             if (appData.quietPlay &&
9433                 (gameMode == IcsPlayingWhite ||
9434                  gameMode == IcsPlayingBlack)) {
9435                 SendToICS(ics_prefix);
9436                 SendToICS("set shout 1\n");
9437             }
9438             nextGameMode = IcsIdle;
9439             ics_user_moved = FALSE;
9440             /* clean up premove.  It's ugly when the game has ended and the
9441              * premove highlights are still on the board.
9442              */
9443             if (gotPremove) {
9444               gotPremove = FALSE;
9445               ClearPremoveHighlights();
9446               DrawPosition(FALSE, boards[currentMove]);
9447             }
9448             if (whosays == GE_ICS) {
9449                 switch (result) {
9450                 case WhiteWins:
9451                     if (gameMode == IcsPlayingWhite)
9452                         PlayIcsWinSound();
9453                     else if(gameMode == IcsPlayingBlack)
9454                         PlayIcsLossSound();
9455                     break;
9456                 case BlackWins:
9457                     if (gameMode == IcsPlayingBlack)
9458                         PlayIcsWinSound();
9459                     else if(gameMode == IcsPlayingWhite)
9460                         PlayIcsLossSound();
9461                     break;
9462                 case GameIsDrawn:
9463                     PlayIcsDrawSound();
9464                     break;
9465                 default:
9466                     PlayIcsUnfinishedSound();
9467                 }
9468             }
9469         } else if (gameMode == EditGame ||
9470                    gameMode == PlayFromGameFile ||
9471                    gameMode == AnalyzeMode ||
9472                    gameMode == AnalyzeFile) {
9473             nextGameMode = gameMode;
9474         } else {
9475             nextGameMode = EndOfGame;
9476         }
9477         pausing = FALSE;
9478         ModeHighlight();
9479     } else {
9480         nextGameMode = gameMode;
9481     }
9482
9483     if (appData.noChessProgram) {
9484         gameMode = nextGameMode;
9485         ModeHighlight();
9486         endingGame = 0; /* [HGM] crash */
9487         return;
9488     }
9489
9490     if (first.reuse) {
9491         /* Put first chess program into idle state */
9492         if (first.pr != NoProc &&
9493             (gameMode == MachinePlaysWhite ||
9494              gameMode == MachinePlaysBlack ||
9495              gameMode == TwoMachinesPlay ||
9496              gameMode == IcsPlayingWhite ||
9497              gameMode == IcsPlayingBlack ||
9498              gameMode == BeginningOfGame)) {
9499             SendToProgram("force\n", &first);
9500             if (first.usePing) {
9501               char buf[MSG_SIZ];
9502               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9503               SendToProgram(buf, &first);
9504             }
9505         }
9506     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9507         /* Kill off first chess program */
9508         if (first.isr != NULL)
9509           RemoveInputSource(first.isr);
9510         first.isr = NULL;
9511
9512         if (first.pr != NoProc) {
9513             ExitAnalyzeMode();
9514             DoSleep( appData.delayBeforeQuit );
9515             SendToProgram("quit\n", &first);
9516             DoSleep( appData.delayAfterQuit );
9517             DestroyChildProcess(first.pr, first.useSigterm);
9518         }
9519         first.pr = NoProc;
9520     }
9521     if (second.reuse) {
9522         /* Put second chess program into idle state */
9523         if (second.pr != NoProc &&
9524             gameMode == TwoMachinesPlay) {
9525             SendToProgram("force\n", &second);
9526             if (second.usePing) {
9527               char buf[MSG_SIZ];
9528               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9529               SendToProgram(buf, &second);
9530             }
9531         }
9532     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9533         /* Kill off second chess program */
9534         if (second.isr != NULL)
9535           RemoveInputSource(second.isr);
9536         second.isr = NULL;
9537
9538         if (second.pr != NoProc) {
9539             DoSleep( appData.delayBeforeQuit );
9540             SendToProgram("quit\n", &second);
9541             DoSleep( appData.delayAfterQuit );
9542             DestroyChildProcess(second.pr, second.useSigterm);
9543         }
9544         second.pr = NoProc;
9545     }
9546
9547     if (matchMode && gameMode == TwoMachinesPlay) {
9548         switch (result) {
9549         case WhiteWins:
9550           if (first.twoMachinesColor[0] == 'w') {
9551             first.matchWins++;
9552           } else {
9553             second.matchWins++;
9554           }
9555           break;
9556         case BlackWins:
9557           if (first.twoMachinesColor[0] == 'b') {
9558             first.matchWins++;
9559           } else {
9560             second.matchWins++;
9561           }
9562           break;
9563         default:
9564           break;
9565         }
9566         if (matchGame < appData.matchGames) {
9567             char *tmp;
9568             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9569                 tmp = first.twoMachinesColor;
9570                 first.twoMachinesColor = second.twoMachinesColor;
9571                 second.twoMachinesColor = tmp;
9572             }
9573             gameMode = nextGameMode;
9574             matchGame++;
9575             if(appData.matchPause>10000 || appData.matchPause<10)
9576                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9577             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9578             endingGame = 0; /* [HGM] crash */
9579             return;
9580         } else {
9581             gameMode = nextGameMode;
9582             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9583                      first.tidy, second.tidy,
9584                      first.matchWins, second.matchWins,
9585                      appData.matchGames - (first.matchWins + second.matchWins));
9586             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9587             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9588                 first.twoMachinesColor = "black\n";
9589                 second.twoMachinesColor = "white\n";
9590             } else {
9591                 first.twoMachinesColor = "white\n";
9592                 second.twoMachinesColor = "black\n";
9593             }
9594         }
9595     }
9596     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9597         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9598       ExitAnalyzeMode();
9599     gameMode = nextGameMode;
9600     ModeHighlight();
9601     endingGame = 0;  /* [HGM] crash */
9602     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9603       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9604         matchMode = FALSE; appData.matchGames = matchGame = 0;
9605         DisplayNote(buf);
9606       }
9607     }
9608 }
9609
9610 /* Assumes program was just initialized (initString sent).
9611    Leaves program in force mode. */
9612 void
9613 FeedMovesToProgram(cps, upto)
9614      ChessProgramState *cps;
9615      int upto;
9616 {
9617     int i;
9618
9619     if (appData.debugMode)
9620       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9621               startedFromSetupPosition ? "position and " : "",
9622               backwardMostMove, upto, cps->which);
9623     if(currentlyInitializedVariant != gameInfo.variant) {
9624       char buf[MSG_SIZ];
9625         // [HGM] variantswitch: make engine aware of new variant
9626         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9627                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9628         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9629         SendToProgram(buf, cps);
9630         currentlyInitializedVariant = gameInfo.variant;
9631     }
9632     SendToProgram("force\n", cps);
9633     if (startedFromSetupPosition) {
9634         SendBoard(cps, backwardMostMove);
9635     if (appData.debugMode) {
9636         fprintf(debugFP, "feedMoves\n");
9637     }
9638     }
9639     for (i = backwardMostMove; i < upto; i++) {
9640         SendMoveToProgram(i, cps);
9641     }
9642 }
9643
9644
9645 void
9646 ResurrectChessProgram()
9647 {
9648      /* The chess program may have exited.
9649         If so, restart it and feed it all the moves made so far. */
9650
9651     if (appData.noChessProgram || first.pr != NoProc) return;
9652
9653     StartChessProgram(&first);
9654     InitChessProgram(&first, FALSE);
9655     FeedMovesToProgram(&first, currentMove);
9656
9657     if (!first.sendTime) {
9658         /* can't tell gnuchess what its clock should read,
9659            so we bow to its notion. */
9660         ResetClocks();
9661         timeRemaining[0][currentMove] = whiteTimeRemaining;
9662         timeRemaining[1][currentMove] = blackTimeRemaining;
9663     }
9664
9665     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9666                 appData.icsEngineAnalyze) && first.analysisSupport) {
9667       SendToProgram("analyze\n", &first);
9668       first.analyzing = TRUE;
9669     }
9670 }
9671
9672 /*
9673  * Button procedures
9674  */
9675 void
9676 Reset(redraw, init)
9677      int redraw, init;
9678 {
9679     int i;
9680
9681     if (appData.debugMode) {
9682         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9683                 redraw, init, gameMode);
9684     }
9685     CleanupTail(); // [HGM] vari: delete any stored variations
9686     pausing = pauseExamInvalid = FALSE;
9687     startedFromSetupPosition = blackPlaysFirst = FALSE;
9688     firstMove = TRUE;
9689     whiteFlag = blackFlag = FALSE;
9690     userOfferedDraw = FALSE;
9691     hintRequested = bookRequested = FALSE;
9692     first.maybeThinking = FALSE;
9693     second.maybeThinking = FALSE;
9694     first.bookSuspend = FALSE; // [HGM] book
9695     second.bookSuspend = FALSE;
9696     thinkOutput[0] = NULLCHAR;
9697     lastHint[0] = NULLCHAR;
9698     ClearGameInfo(&gameInfo);
9699     gameInfo.variant = StringToVariant(appData.variant);
9700     ics_user_moved = ics_clock_paused = FALSE;
9701     ics_getting_history = H_FALSE;
9702     ics_gamenum = -1;
9703     white_holding[0] = black_holding[0] = NULLCHAR;
9704     ClearProgramStats();
9705     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9706
9707     ResetFrontEnd();
9708     ClearHighlights();
9709     flipView = appData.flipView;
9710     ClearPremoveHighlights();
9711     gotPremove = FALSE;
9712     alarmSounded = FALSE;
9713
9714     GameEnds(EndOfFile, NULL, GE_PLAYER);
9715     if(appData.serverMovesName != NULL) {
9716         /* [HGM] prepare to make moves file for broadcasting */
9717         clock_t t = clock();
9718         if(serverMoves != NULL) fclose(serverMoves);
9719         serverMoves = fopen(appData.serverMovesName, "r");
9720         if(serverMoves != NULL) {
9721             fclose(serverMoves);
9722             /* delay 15 sec before overwriting, so all clients can see end */
9723             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9724         }
9725         serverMoves = fopen(appData.serverMovesName, "w");
9726     }
9727
9728     ExitAnalyzeMode();
9729     gameMode = BeginningOfGame;
9730     ModeHighlight();
9731     if(appData.icsActive) gameInfo.variant = VariantNormal;
9732     currentMove = forwardMostMove = backwardMostMove = 0;
9733     InitPosition(redraw);
9734     for (i = 0; i < MAX_MOVES; i++) {
9735         if (commentList[i] != NULL) {
9736             free(commentList[i]);
9737             commentList[i] = NULL;
9738         }
9739     }
9740     ResetClocks();
9741     timeRemaining[0][0] = whiteTimeRemaining;
9742     timeRemaining[1][0] = blackTimeRemaining;
9743     if (first.pr == NULL) {
9744         StartChessProgram(&first);
9745     }
9746     if (init) {
9747             InitChessProgram(&first, startedFromSetupPosition);
9748     }
9749     DisplayTitle("");
9750     DisplayMessage("", "");
9751     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9752     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9753 }
9754
9755 void
9756 AutoPlayGameLoop()
9757 {
9758     for (;;) {
9759         if (!AutoPlayOneMove())
9760           return;
9761         if (matchMode || appData.timeDelay == 0)
9762           continue;
9763         if (appData.timeDelay < 0)
9764           return;
9765         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9766         break;
9767     }
9768 }
9769
9770
9771 int
9772 AutoPlayOneMove()
9773 {
9774     int fromX, fromY, toX, toY;
9775
9776     if (appData.debugMode) {
9777       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9778     }
9779
9780     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9781       return FALSE;
9782
9783     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9784       pvInfoList[currentMove].depth = programStats.depth;
9785       pvInfoList[currentMove].score = programStats.score;
9786       pvInfoList[currentMove].time  = 0;
9787       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9788     }
9789
9790     if (currentMove >= forwardMostMove) {
9791       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9792       gameMode = EditGame;
9793       ModeHighlight();
9794
9795       /* [AS] Clear current move marker at the end of a game */
9796       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9797
9798       return FALSE;
9799     }
9800
9801     toX = moveList[currentMove][2] - AAA;
9802     toY = moveList[currentMove][3] - ONE;
9803
9804     if (moveList[currentMove][1] == '@') {
9805         if (appData.highlightLastMove) {
9806             SetHighlights(-1, -1, toX, toY);
9807         }
9808     } else {
9809         fromX = moveList[currentMove][0] - AAA;
9810         fromY = moveList[currentMove][1] - ONE;
9811
9812         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9813
9814         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9815
9816         if (appData.highlightLastMove) {
9817             SetHighlights(fromX, fromY, toX, toY);
9818         }
9819     }
9820     DisplayMove(currentMove);
9821     SendMoveToProgram(currentMove++, &first);
9822     DisplayBothClocks();
9823     DrawPosition(FALSE, boards[currentMove]);
9824     // [HGM] PV info: always display, routine tests if empty
9825     DisplayComment(currentMove - 1, commentList[currentMove]);
9826     return TRUE;
9827 }
9828
9829
9830 int
9831 LoadGameOneMove(readAhead)
9832      ChessMove readAhead;
9833 {
9834     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9835     char promoChar = NULLCHAR;
9836     ChessMove moveType;
9837     char move[MSG_SIZ];
9838     char *p, *q;
9839
9840     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9841         gameMode != AnalyzeMode && gameMode != Training) {
9842         gameFileFP = NULL;
9843         return FALSE;
9844     }
9845
9846     yyboardindex = forwardMostMove;
9847     if (readAhead != EndOfFile) {
9848       moveType = readAhead;
9849     } else {
9850       if (gameFileFP == NULL)
9851           return FALSE;
9852       moveType = (ChessMove) Myylex();
9853     }
9854
9855     done = FALSE;
9856     switch (moveType) {
9857       case Comment:
9858         if (appData.debugMode)
9859           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9860         p = yy_text;
9861
9862         /* append the comment but don't display it */
9863         AppendComment(currentMove, p, FALSE);
9864         return TRUE;
9865
9866       case WhiteCapturesEnPassant:
9867       case BlackCapturesEnPassant:
9868       case WhitePromotion:
9869       case BlackPromotion:
9870       case WhiteNonPromotion:
9871       case BlackNonPromotion:
9872       case NormalMove:
9873       case WhiteKingSideCastle:
9874       case WhiteQueenSideCastle:
9875       case BlackKingSideCastle:
9876       case BlackQueenSideCastle:
9877       case WhiteKingSideCastleWild:
9878       case WhiteQueenSideCastleWild:
9879       case BlackKingSideCastleWild:
9880       case BlackQueenSideCastleWild:
9881       /* PUSH Fabien */
9882       case WhiteHSideCastleFR:
9883       case WhiteASideCastleFR:
9884       case BlackHSideCastleFR:
9885       case BlackASideCastleFR:
9886       /* POP Fabien */
9887         if (appData.debugMode)
9888           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9889         fromX = currentMoveString[0] - AAA;
9890         fromY = currentMoveString[1] - ONE;
9891         toX = currentMoveString[2] - AAA;
9892         toY = currentMoveString[3] - ONE;
9893         promoChar = currentMoveString[4];
9894         break;
9895
9896       case WhiteDrop:
9897       case BlackDrop:
9898         if (appData.debugMode)
9899           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9900         fromX = moveType == WhiteDrop ?
9901           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9902         (int) CharToPiece(ToLower(currentMoveString[0]));
9903         fromY = DROP_RANK;
9904         toX = currentMoveString[2] - AAA;
9905         toY = currentMoveString[3] - ONE;
9906         break;
9907
9908       case WhiteWins:
9909       case BlackWins:
9910       case GameIsDrawn:
9911       case GameUnfinished:
9912         if (appData.debugMode)
9913           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9914         p = strchr(yy_text, '{');
9915         if (p == NULL) p = strchr(yy_text, '(');
9916         if (p == NULL) {
9917             p = yy_text;
9918             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9919         } else {
9920             q = strchr(p, *p == '{' ? '}' : ')');
9921             if (q != NULL) *q = NULLCHAR;
9922             p++;
9923         }
9924         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9925         GameEnds(moveType, p, GE_FILE);
9926         done = TRUE;
9927         if (cmailMsgLoaded) {
9928             ClearHighlights();
9929             flipView = WhiteOnMove(currentMove);
9930             if (moveType == GameUnfinished) flipView = !flipView;
9931             if (appData.debugMode)
9932               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9933         }
9934         break;
9935
9936       case EndOfFile:
9937         if (appData.debugMode)
9938           fprintf(debugFP, "Parser hit end of file\n");
9939         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9940           case MT_NONE:
9941           case MT_CHECK:
9942             break;
9943           case MT_CHECKMATE:
9944           case MT_STAINMATE:
9945             if (WhiteOnMove(currentMove)) {
9946                 GameEnds(BlackWins, "Black mates", GE_FILE);
9947             } else {
9948                 GameEnds(WhiteWins, "White mates", GE_FILE);
9949             }
9950             break;
9951           case MT_STALEMATE:
9952             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9953             break;
9954         }
9955         done = TRUE;
9956         break;
9957
9958       case MoveNumberOne:
9959         if (lastLoadGameStart == GNUChessGame) {
9960             /* GNUChessGames have numbers, but they aren't move numbers */
9961             if (appData.debugMode)
9962               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9963                       yy_text, (int) moveType);
9964             return LoadGameOneMove(EndOfFile); /* tail recursion */
9965         }
9966         /* else fall thru */
9967
9968       case XBoardGame:
9969       case GNUChessGame:
9970       case PGNTag:
9971         /* Reached start of next game in file */
9972         if (appData.debugMode)
9973           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9974         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9975           case MT_NONE:
9976           case MT_CHECK:
9977             break;
9978           case MT_CHECKMATE:
9979           case MT_STAINMATE:
9980             if (WhiteOnMove(currentMove)) {
9981                 GameEnds(BlackWins, "Black mates", GE_FILE);
9982             } else {
9983                 GameEnds(WhiteWins, "White mates", GE_FILE);
9984             }
9985             break;
9986           case MT_STALEMATE:
9987             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9988             break;
9989         }
9990         done = TRUE;
9991         break;
9992
9993       case PositionDiagram:     /* should not happen; ignore */
9994       case ElapsedTime:         /* ignore */
9995       case NAG:                 /* ignore */
9996         if (appData.debugMode)
9997           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9998                   yy_text, (int) moveType);
9999         return LoadGameOneMove(EndOfFile); /* tail recursion */
10000
10001       case IllegalMove:
10002         if (appData.testLegality) {
10003             if (appData.debugMode)
10004               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10005             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10006                     (forwardMostMove / 2) + 1,
10007                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10008             DisplayError(move, 0);
10009             done = TRUE;
10010         } else {
10011             if (appData.debugMode)
10012               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10013                       yy_text, currentMoveString);
10014             fromX = currentMoveString[0] - AAA;
10015             fromY = currentMoveString[1] - ONE;
10016             toX = currentMoveString[2] - AAA;
10017             toY = currentMoveString[3] - ONE;
10018             promoChar = currentMoveString[4];
10019         }
10020         break;
10021
10022       case AmbiguousMove:
10023         if (appData.debugMode)
10024           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10025         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10026                 (forwardMostMove / 2) + 1,
10027                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10028         DisplayError(move, 0);
10029         done = TRUE;
10030         break;
10031
10032       default:
10033       case ImpossibleMove:
10034         if (appData.debugMode)
10035           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10036         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10037                 (forwardMostMove / 2) + 1,
10038                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10039         DisplayError(move, 0);
10040         done = TRUE;
10041         break;
10042     }
10043
10044     if (done) {
10045         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10046             DrawPosition(FALSE, boards[currentMove]);
10047             DisplayBothClocks();
10048             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10049               DisplayComment(currentMove - 1, commentList[currentMove]);
10050         }
10051         (void) StopLoadGameTimer();
10052         gameFileFP = NULL;
10053         cmailOldMove = forwardMostMove;
10054         return FALSE;
10055     } else {
10056         /* currentMoveString is set as a side-effect of yylex */
10057
10058         thinkOutput[0] = NULLCHAR;
10059         MakeMove(fromX, fromY, toX, toY, promoChar);
10060         currentMove = forwardMostMove;
10061         return TRUE;
10062     }
10063 }
10064
10065 /* Load the nth game from the given file */
10066 int
10067 LoadGameFromFile(filename, n, title, useList)
10068      char *filename;
10069      int n;
10070      char *title;
10071      /*Boolean*/ int useList;
10072 {
10073     FILE *f;
10074     char buf[MSG_SIZ];
10075
10076     if (strcmp(filename, "-") == 0) {
10077         f = stdin;
10078         title = "stdin";
10079     } else {
10080         f = fopen(filename, "rb");
10081         if (f == NULL) {
10082           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10083             DisplayError(buf, errno);
10084             return FALSE;
10085         }
10086     }
10087     if (fseek(f, 0, 0) == -1) {
10088         /* f is not seekable; probably a pipe */
10089         useList = FALSE;
10090     }
10091     if (useList && n == 0) {
10092         int error = GameListBuild(f);
10093         if (error) {
10094             DisplayError(_("Cannot build game list"), error);
10095         } else if (!ListEmpty(&gameList) &&
10096                    ((ListGame *) gameList.tailPred)->number > 1) {
10097             GameListPopUp(f, title);
10098             return TRUE;
10099         }
10100         GameListDestroy();
10101         n = 1;
10102     }
10103     if (n == 0) n = 1;
10104     return LoadGame(f, n, title, FALSE);
10105 }
10106
10107
10108 void
10109 MakeRegisteredMove()
10110 {
10111     int fromX, fromY, toX, toY;
10112     char promoChar;
10113     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10114         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10115           case CMAIL_MOVE:
10116           case CMAIL_DRAW:
10117             if (appData.debugMode)
10118               fprintf(debugFP, "Restoring %s for game %d\n",
10119                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10120
10121             thinkOutput[0] = NULLCHAR;
10122             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10123             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10124             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10125             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10126             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10127             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10128             MakeMove(fromX, fromY, toX, toY, promoChar);
10129             ShowMove(fromX, fromY, toX, toY);
10130
10131             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10132               case MT_NONE:
10133               case MT_CHECK:
10134                 break;
10135
10136               case MT_CHECKMATE:
10137               case MT_STAINMATE:
10138                 if (WhiteOnMove(currentMove)) {
10139                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10140                 } else {
10141                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10142                 }
10143                 break;
10144
10145               case MT_STALEMATE:
10146                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10147                 break;
10148             }
10149
10150             break;
10151
10152           case CMAIL_RESIGN:
10153             if (WhiteOnMove(currentMove)) {
10154                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10155             } else {
10156                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10157             }
10158             break;
10159
10160           case CMAIL_ACCEPT:
10161             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10162             break;
10163
10164           default:
10165             break;
10166         }
10167     }
10168
10169     return;
10170 }
10171
10172 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10173 int
10174 CmailLoadGame(f, gameNumber, title, useList)
10175      FILE *f;
10176      int gameNumber;
10177      char *title;
10178      int useList;
10179 {
10180     int retVal;
10181
10182     if (gameNumber > nCmailGames) {
10183         DisplayError(_("No more games in this message"), 0);
10184         return FALSE;
10185     }
10186     if (f == lastLoadGameFP) {
10187         int offset = gameNumber - lastLoadGameNumber;
10188         if (offset == 0) {
10189             cmailMsg[0] = NULLCHAR;
10190             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10191                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10192                 nCmailMovesRegistered--;
10193             }
10194             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10195             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10196                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10197             }
10198         } else {
10199             if (! RegisterMove()) return FALSE;
10200         }
10201     }
10202
10203     retVal = LoadGame(f, gameNumber, title, useList);
10204
10205     /* Make move registered during previous look at this game, if any */
10206     MakeRegisteredMove();
10207
10208     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10209         commentList[currentMove]
10210           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10211         DisplayComment(currentMove - 1, commentList[currentMove]);
10212     }
10213
10214     return retVal;
10215 }
10216
10217 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10218 int
10219 ReloadGame(offset)
10220      int offset;
10221 {
10222     int gameNumber = lastLoadGameNumber + offset;
10223     if (lastLoadGameFP == NULL) {
10224         DisplayError(_("No game has been loaded yet"), 0);
10225         return FALSE;
10226     }
10227     if (gameNumber <= 0) {
10228         DisplayError(_("Can't back up any further"), 0);
10229         return FALSE;
10230     }
10231     if (cmailMsgLoaded) {
10232         return CmailLoadGame(lastLoadGameFP, gameNumber,
10233                              lastLoadGameTitle, lastLoadGameUseList);
10234     } else {
10235         return LoadGame(lastLoadGameFP, gameNumber,
10236                         lastLoadGameTitle, lastLoadGameUseList);
10237     }
10238 }
10239
10240
10241
10242 /* Load the nth game from open file f */
10243 int
10244 LoadGame(f, gameNumber, title, useList)
10245      FILE *f;
10246      int gameNumber;
10247      char *title;
10248      int useList;
10249 {
10250     ChessMove cm;
10251     char buf[MSG_SIZ];
10252     int gn = gameNumber;
10253     ListGame *lg = NULL;
10254     int numPGNTags = 0;
10255     int err;
10256     GameMode oldGameMode;
10257     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10258
10259     if (appData.debugMode)
10260         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10261
10262     if (gameMode == Training )
10263         SetTrainingModeOff();
10264
10265     oldGameMode = gameMode;
10266     if (gameMode != BeginningOfGame) {
10267       Reset(FALSE, TRUE);
10268     }
10269
10270     gameFileFP = f;
10271     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10272         fclose(lastLoadGameFP);
10273     }
10274
10275     if (useList) {
10276         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10277
10278         if (lg) {
10279             fseek(f, lg->offset, 0);
10280             GameListHighlight(gameNumber);
10281             gn = 1;
10282         }
10283         else {
10284             DisplayError(_("Game number out of range"), 0);
10285             return FALSE;
10286         }
10287     } else {
10288         GameListDestroy();
10289         if (fseek(f, 0, 0) == -1) {
10290             if (f == lastLoadGameFP ?
10291                 gameNumber == lastLoadGameNumber + 1 :
10292                 gameNumber == 1) {
10293                 gn = 1;
10294             } else {
10295                 DisplayError(_("Can't seek on game file"), 0);
10296                 return FALSE;
10297             }
10298         }
10299     }
10300     lastLoadGameFP = f;
10301     lastLoadGameNumber = gameNumber;
10302     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10303     lastLoadGameUseList = useList;
10304
10305     yynewfile(f);
10306
10307     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10308       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10309                 lg->gameInfo.black);
10310             DisplayTitle(buf);
10311     } else if (*title != NULLCHAR) {
10312         if (gameNumber > 1) {
10313           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10314             DisplayTitle(buf);
10315         } else {
10316             DisplayTitle(title);
10317         }
10318     }
10319
10320     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10321         gameMode = PlayFromGameFile;
10322         ModeHighlight();
10323     }
10324
10325     currentMove = forwardMostMove = backwardMostMove = 0;
10326     CopyBoard(boards[0], initialPosition);
10327     StopClocks();
10328
10329     /*
10330      * Skip the first gn-1 games in the file.
10331      * Also skip over anything that precedes an identifiable
10332      * start of game marker, to avoid being confused by
10333      * garbage at the start of the file.  Currently
10334      * recognized start of game markers are the move number "1",
10335      * the pattern "gnuchess .* game", the pattern
10336      * "^[#;%] [^ ]* game file", and a PGN tag block.
10337      * A game that starts with one of the latter two patterns
10338      * will also have a move number 1, possibly
10339      * following a position diagram.
10340      * 5-4-02: Let's try being more lenient and allowing a game to
10341      * start with an unnumbered move.  Does that break anything?
10342      */
10343     cm = lastLoadGameStart = EndOfFile;
10344     while (gn > 0) {
10345         yyboardindex = forwardMostMove;
10346         cm = (ChessMove) Myylex();
10347         switch (cm) {
10348           case EndOfFile:
10349             if (cmailMsgLoaded) {
10350                 nCmailGames = CMAIL_MAX_GAMES - gn;
10351             } else {
10352                 Reset(TRUE, TRUE);
10353                 DisplayError(_("Game not found in file"), 0);
10354             }
10355             return FALSE;
10356
10357           case GNUChessGame:
10358           case XBoardGame:
10359             gn--;
10360             lastLoadGameStart = cm;
10361             break;
10362
10363           case MoveNumberOne:
10364             switch (lastLoadGameStart) {
10365               case GNUChessGame:
10366               case XBoardGame:
10367               case PGNTag:
10368                 break;
10369               case MoveNumberOne:
10370               case EndOfFile:
10371                 gn--;           /* count this game */
10372                 lastLoadGameStart = cm;
10373                 break;
10374               default:
10375                 /* impossible */
10376                 break;
10377             }
10378             break;
10379
10380           case PGNTag:
10381             switch (lastLoadGameStart) {
10382               case GNUChessGame:
10383               case PGNTag:
10384               case MoveNumberOne:
10385               case EndOfFile:
10386                 gn--;           /* count this game */
10387                 lastLoadGameStart = cm;
10388                 break;
10389               case XBoardGame:
10390                 lastLoadGameStart = cm; /* game counted already */
10391                 break;
10392               default:
10393                 /* impossible */
10394                 break;
10395             }
10396             if (gn > 0) {
10397                 do {
10398                     yyboardindex = forwardMostMove;
10399                     cm = (ChessMove) Myylex();
10400                 } while (cm == PGNTag || cm == Comment);
10401             }
10402             break;
10403
10404           case WhiteWins:
10405           case BlackWins:
10406           case GameIsDrawn:
10407             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10408                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10409                     != CMAIL_OLD_RESULT) {
10410                     nCmailResults ++ ;
10411                     cmailResult[  CMAIL_MAX_GAMES
10412                                 - gn - 1] = CMAIL_OLD_RESULT;
10413                 }
10414             }
10415             break;
10416
10417           case NormalMove:
10418             /* Only a NormalMove can be at the start of a game
10419              * without a position diagram. */
10420             if (lastLoadGameStart == EndOfFile ) {
10421               gn--;
10422               lastLoadGameStart = MoveNumberOne;
10423             }
10424             break;
10425
10426           default:
10427             break;
10428         }
10429     }
10430
10431     if (appData.debugMode)
10432       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10433
10434     if (cm == XBoardGame) {
10435         /* Skip any header junk before position diagram and/or move 1 */
10436         for (;;) {
10437             yyboardindex = forwardMostMove;
10438             cm = (ChessMove) Myylex();
10439
10440             if (cm == EndOfFile ||
10441                 cm == GNUChessGame || cm == XBoardGame) {
10442                 /* Empty game; pretend end-of-file and handle later */
10443                 cm = EndOfFile;
10444                 break;
10445             }
10446
10447             if (cm == MoveNumberOne || cm == PositionDiagram ||
10448                 cm == PGNTag || cm == Comment)
10449               break;
10450         }
10451     } else if (cm == GNUChessGame) {
10452         if (gameInfo.event != NULL) {
10453             free(gameInfo.event);
10454         }
10455         gameInfo.event = StrSave(yy_text);
10456     }
10457
10458     startedFromSetupPosition = FALSE;
10459     while (cm == PGNTag) {
10460         if (appData.debugMode)
10461           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10462         err = ParsePGNTag(yy_text, &gameInfo);
10463         if (!err) numPGNTags++;
10464
10465         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10466         if(gameInfo.variant != oldVariant) {
10467             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10468             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10469             InitPosition(TRUE);
10470             oldVariant = gameInfo.variant;
10471             if (appData.debugMode)
10472               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10473         }
10474
10475
10476         if (gameInfo.fen != NULL) {
10477           Board initial_position;
10478           startedFromSetupPosition = TRUE;
10479           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10480             Reset(TRUE, TRUE);
10481             DisplayError(_("Bad FEN position in file"), 0);
10482             return FALSE;
10483           }
10484           CopyBoard(boards[0], initial_position);
10485           if (blackPlaysFirst) {
10486             currentMove = forwardMostMove = backwardMostMove = 1;
10487             CopyBoard(boards[1], initial_position);
10488             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10489             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10490             timeRemaining[0][1] = whiteTimeRemaining;
10491             timeRemaining[1][1] = blackTimeRemaining;
10492             if (commentList[0] != NULL) {
10493               commentList[1] = commentList[0];
10494               commentList[0] = NULL;
10495             }
10496           } else {
10497             currentMove = forwardMostMove = backwardMostMove = 0;
10498           }
10499           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10500           {   int i;
10501               initialRulePlies = FENrulePlies;
10502               for( i=0; i< nrCastlingRights; i++ )
10503                   initialRights[i] = initial_position[CASTLING][i];
10504           }
10505           yyboardindex = forwardMostMove;
10506           free(gameInfo.fen);
10507           gameInfo.fen = NULL;
10508         }
10509
10510         yyboardindex = forwardMostMove;
10511         cm = (ChessMove) Myylex();
10512
10513         /* Handle comments interspersed among the tags */
10514         while (cm == Comment) {
10515             char *p;
10516             if (appData.debugMode)
10517               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10518             p = yy_text;
10519             AppendComment(currentMove, p, FALSE);
10520             yyboardindex = forwardMostMove;
10521             cm = (ChessMove) Myylex();
10522         }
10523     }
10524
10525     /* don't rely on existence of Event tag since if game was
10526      * pasted from clipboard the Event tag may not exist
10527      */
10528     if (numPGNTags > 0){
10529         char *tags;
10530         if (gameInfo.variant == VariantNormal) {
10531           VariantClass v = StringToVariant(gameInfo.event);
10532           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10533           if(v < VariantShogi) gameInfo.variant = v;
10534         }
10535         if (!matchMode) {
10536           if( appData.autoDisplayTags ) {
10537             tags = PGNTags(&gameInfo);
10538             TagsPopUp(tags, CmailMsg());
10539             free(tags);
10540           }
10541         }
10542     } else {
10543         /* Make something up, but don't display it now */
10544         SetGameInfo();
10545         TagsPopDown();
10546     }
10547
10548     if (cm == PositionDiagram) {
10549         int i, j;
10550         char *p;
10551         Board initial_position;
10552
10553         if (appData.debugMode)
10554           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10555
10556         if (!startedFromSetupPosition) {
10557             p = yy_text;
10558             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10559               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10560                 switch (*p) {
10561                   case '{':
10562                   case '[':
10563                   case '-':
10564                   case ' ':
10565                   case '\t':
10566                   case '\n':
10567                   case '\r':
10568                     break;
10569                   default:
10570                     initial_position[i][j++] = CharToPiece(*p);
10571                     break;
10572                 }
10573             while (*p == ' ' || *p == '\t' ||
10574                    *p == '\n' || *p == '\r') p++;
10575
10576             if (strncmp(p, "black", strlen("black"))==0)
10577               blackPlaysFirst = TRUE;
10578             else
10579               blackPlaysFirst = FALSE;
10580             startedFromSetupPosition = TRUE;
10581
10582             CopyBoard(boards[0], initial_position);
10583             if (blackPlaysFirst) {
10584                 currentMove = forwardMostMove = backwardMostMove = 1;
10585                 CopyBoard(boards[1], initial_position);
10586                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10587                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10588                 timeRemaining[0][1] = whiteTimeRemaining;
10589                 timeRemaining[1][1] = blackTimeRemaining;
10590                 if (commentList[0] != NULL) {
10591                     commentList[1] = commentList[0];
10592                     commentList[0] = NULL;
10593                 }
10594             } else {
10595                 currentMove = forwardMostMove = backwardMostMove = 0;
10596             }
10597         }
10598         yyboardindex = forwardMostMove;
10599         cm = (ChessMove) Myylex();
10600     }
10601
10602     if (first.pr == NoProc) {
10603         StartChessProgram(&first);
10604     }
10605     InitChessProgram(&first, FALSE);
10606     SendToProgram("force\n", &first);
10607     if (startedFromSetupPosition) {
10608         SendBoard(&first, forwardMostMove);
10609     if (appData.debugMode) {
10610         fprintf(debugFP, "Load Game\n");
10611     }
10612         DisplayBothClocks();
10613     }
10614
10615     /* [HGM] server: flag to write setup moves in broadcast file as one */
10616     loadFlag = appData.suppressLoadMoves;
10617
10618     while (cm == Comment) {
10619         char *p;
10620         if (appData.debugMode)
10621           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10622         p = yy_text;
10623         AppendComment(currentMove, p, FALSE);
10624         yyboardindex = forwardMostMove;
10625         cm = (ChessMove) Myylex();
10626     }
10627
10628     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10629         cm == WhiteWins || cm == BlackWins ||
10630         cm == GameIsDrawn || cm == GameUnfinished) {
10631         DisplayMessage("", _("No moves in game"));
10632         if (cmailMsgLoaded) {
10633             if (appData.debugMode)
10634               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10635             ClearHighlights();
10636             flipView = FALSE;
10637         }
10638         DrawPosition(FALSE, boards[currentMove]);
10639         DisplayBothClocks();
10640         gameMode = EditGame;
10641         ModeHighlight();
10642         gameFileFP = NULL;
10643         cmailOldMove = 0;
10644         return TRUE;
10645     }
10646
10647     // [HGM] PV info: routine tests if comment empty
10648     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10649         DisplayComment(currentMove - 1, commentList[currentMove]);
10650     }
10651     if (!matchMode && appData.timeDelay != 0)
10652       DrawPosition(FALSE, boards[currentMove]);
10653
10654     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10655       programStats.ok_to_send = 1;
10656     }
10657
10658     /* if the first token after the PGN tags is a move
10659      * and not move number 1, retrieve it from the parser
10660      */
10661     if (cm != MoveNumberOne)
10662         LoadGameOneMove(cm);
10663
10664     /* load the remaining moves from the file */
10665     while (LoadGameOneMove(EndOfFile)) {
10666       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10667       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10668     }
10669
10670     /* rewind to the start of the game */
10671     currentMove = backwardMostMove;
10672
10673     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10674
10675     if (oldGameMode == AnalyzeFile ||
10676         oldGameMode == AnalyzeMode) {
10677       AnalyzeFileEvent();
10678     }
10679
10680     if (matchMode || appData.timeDelay == 0) {
10681       ToEndEvent();
10682       gameMode = EditGame;
10683       ModeHighlight();
10684     } else if (appData.timeDelay > 0) {
10685       AutoPlayGameLoop();
10686     }
10687
10688     if (appData.debugMode)
10689         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10690
10691     loadFlag = 0; /* [HGM] true game starts */
10692     return TRUE;
10693 }
10694
10695 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10696 int
10697 ReloadPosition(offset)
10698      int offset;
10699 {
10700     int positionNumber = lastLoadPositionNumber + offset;
10701     if (lastLoadPositionFP == NULL) {
10702         DisplayError(_("No position has been loaded yet"), 0);
10703         return FALSE;
10704     }
10705     if (positionNumber <= 0) {
10706         DisplayError(_("Can't back up any further"), 0);
10707         return FALSE;
10708     }
10709     return LoadPosition(lastLoadPositionFP, positionNumber,
10710                         lastLoadPositionTitle);
10711 }
10712
10713 /* Load the nth position from the given file */
10714 int
10715 LoadPositionFromFile(filename, n, title)
10716      char *filename;
10717      int n;
10718      char *title;
10719 {
10720     FILE *f;
10721     char buf[MSG_SIZ];
10722
10723     if (strcmp(filename, "-") == 0) {
10724         return LoadPosition(stdin, n, "stdin");
10725     } else {
10726         f = fopen(filename, "rb");
10727         if (f == NULL) {
10728             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10729             DisplayError(buf, errno);
10730             return FALSE;
10731         } else {
10732             return LoadPosition(f, n, title);
10733         }
10734     }
10735 }
10736
10737 /* Load the nth position from the given open file, and close it */
10738 int
10739 LoadPosition(f, positionNumber, title)
10740      FILE *f;
10741      int positionNumber;
10742      char *title;
10743 {
10744     char *p, line[MSG_SIZ];
10745     Board initial_position;
10746     int i, j, fenMode, pn;
10747
10748     if (gameMode == Training )
10749         SetTrainingModeOff();
10750
10751     if (gameMode != BeginningOfGame) {
10752         Reset(FALSE, TRUE);
10753     }
10754     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10755         fclose(lastLoadPositionFP);
10756     }
10757     if (positionNumber == 0) positionNumber = 1;
10758     lastLoadPositionFP = f;
10759     lastLoadPositionNumber = positionNumber;
10760     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10761     if (first.pr == NoProc) {
10762       StartChessProgram(&first);
10763       InitChessProgram(&first, FALSE);
10764     }
10765     pn = positionNumber;
10766     if (positionNumber < 0) {
10767         /* Negative position number means to seek to that byte offset */
10768         if (fseek(f, -positionNumber, 0) == -1) {
10769             DisplayError(_("Can't seek on position file"), 0);
10770             return FALSE;
10771         };
10772         pn = 1;
10773     } else {
10774         if (fseek(f, 0, 0) == -1) {
10775             if (f == lastLoadPositionFP ?
10776                 positionNumber == lastLoadPositionNumber + 1 :
10777                 positionNumber == 1) {
10778                 pn = 1;
10779             } else {
10780                 DisplayError(_("Can't seek on position file"), 0);
10781                 return FALSE;
10782             }
10783         }
10784     }
10785     /* See if this file is FEN or old-style xboard */
10786     if (fgets(line, MSG_SIZ, f) == NULL) {
10787         DisplayError(_("Position not found in file"), 0);
10788         return FALSE;
10789     }
10790     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10791     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10792
10793     if (pn >= 2) {
10794         if (fenMode || line[0] == '#') pn--;
10795         while (pn > 0) {
10796             /* skip positions before number pn */
10797             if (fgets(line, MSG_SIZ, f) == NULL) {
10798                 Reset(TRUE, TRUE);
10799                 DisplayError(_("Position not found in file"), 0);
10800                 return FALSE;
10801             }
10802             if (fenMode || line[0] == '#') pn--;
10803         }
10804     }
10805
10806     if (fenMode) {
10807         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10808             DisplayError(_("Bad FEN position in file"), 0);
10809             return FALSE;
10810         }
10811     } else {
10812         (void) fgets(line, MSG_SIZ, f);
10813         (void) fgets(line, MSG_SIZ, f);
10814
10815         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10816             (void) fgets(line, MSG_SIZ, f);
10817             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10818                 if (*p == ' ')
10819                   continue;
10820                 initial_position[i][j++] = CharToPiece(*p);
10821             }
10822         }
10823
10824         blackPlaysFirst = FALSE;
10825         if (!feof(f)) {
10826             (void) fgets(line, MSG_SIZ, f);
10827             if (strncmp(line, "black", strlen("black"))==0)
10828               blackPlaysFirst = TRUE;
10829         }
10830     }
10831     startedFromSetupPosition = TRUE;
10832
10833     SendToProgram("force\n", &first);
10834     CopyBoard(boards[0], initial_position);
10835     if (blackPlaysFirst) {
10836         currentMove = forwardMostMove = backwardMostMove = 1;
10837         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10838         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10839         CopyBoard(boards[1], initial_position);
10840         DisplayMessage("", _("Black to play"));
10841     } else {
10842         currentMove = forwardMostMove = backwardMostMove = 0;
10843         DisplayMessage("", _("White to play"));
10844     }
10845     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10846     SendBoard(&first, forwardMostMove);
10847     if (appData.debugMode) {
10848 int i, j;
10849   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10850   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10851         fprintf(debugFP, "Load Position\n");
10852     }
10853
10854     if (positionNumber > 1) {
10855       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10856         DisplayTitle(line);
10857     } else {
10858         DisplayTitle(title);
10859     }
10860     gameMode = EditGame;
10861     ModeHighlight();
10862     ResetClocks();
10863     timeRemaining[0][1] = whiteTimeRemaining;
10864     timeRemaining[1][1] = blackTimeRemaining;
10865     DrawPosition(FALSE, boards[currentMove]);
10866
10867     return TRUE;
10868 }
10869
10870
10871 void
10872 CopyPlayerNameIntoFileName(dest, src)
10873      char **dest, *src;
10874 {
10875     while (*src != NULLCHAR && *src != ',') {
10876         if (*src == ' ') {
10877             *(*dest)++ = '_';
10878             src++;
10879         } else {
10880             *(*dest)++ = *src++;
10881         }
10882     }
10883 }
10884
10885 char *DefaultFileName(ext)
10886      char *ext;
10887 {
10888     static char def[MSG_SIZ];
10889     char *p;
10890
10891     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10892         p = def;
10893         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10894         *p++ = '-';
10895         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10896         *p++ = '.';
10897         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10898     } else {
10899         def[0] = NULLCHAR;
10900     }
10901     return def;
10902 }
10903
10904 /* Save the current game to the given file */
10905 int
10906 SaveGameToFile(filename, append)
10907      char *filename;
10908      int append;
10909 {
10910     FILE *f;
10911     char buf[MSG_SIZ];
10912
10913     if (strcmp(filename, "-") == 0) {
10914         return SaveGame(stdout, 0, NULL);
10915     } else {
10916         f = fopen(filename, append ? "a" : "w");
10917         if (f == NULL) {
10918             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10919             DisplayError(buf, errno);
10920             return FALSE;
10921         } else {
10922             return SaveGame(f, 0, NULL);
10923         }
10924     }
10925 }
10926
10927 char *
10928 SavePart(str)
10929      char *str;
10930 {
10931     static char buf[MSG_SIZ];
10932     char *p;
10933
10934     p = strchr(str, ' ');
10935     if (p == NULL) return str;
10936     strncpy(buf, str, p - str);
10937     buf[p - str] = NULLCHAR;
10938     return buf;
10939 }
10940
10941 #define PGN_MAX_LINE 75
10942
10943 #define PGN_SIDE_WHITE  0
10944 #define PGN_SIDE_BLACK  1
10945
10946 /* [AS] */
10947 static int FindFirstMoveOutOfBook( int side )
10948 {
10949     int result = -1;
10950
10951     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10952         int index = backwardMostMove;
10953         int has_book_hit = 0;
10954
10955         if( (index % 2) != side ) {
10956             index++;
10957         }
10958
10959         while( index < forwardMostMove ) {
10960             /* Check to see if engine is in book */
10961             int depth = pvInfoList[index].depth;
10962             int score = pvInfoList[index].score;
10963             int in_book = 0;
10964
10965             if( depth <= 2 ) {
10966                 in_book = 1;
10967             }
10968             else if( score == 0 && depth == 63 ) {
10969                 in_book = 1; /* Zappa */
10970             }
10971             else if( score == 2 && depth == 99 ) {
10972                 in_book = 1; /* Abrok */
10973             }
10974
10975             has_book_hit += in_book;
10976
10977             if( ! in_book ) {
10978                 result = index;
10979
10980                 break;
10981             }
10982
10983             index += 2;
10984         }
10985     }
10986
10987     return result;
10988 }
10989
10990 /* [AS] */
10991 void GetOutOfBookInfo( char * buf )
10992 {
10993     int oob[2];
10994     int i;
10995     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10996
10997     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10998     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10999
11000     *buf = '\0';
11001
11002     if( oob[0] >= 0 || oob[1] >= 0 ) {
11003         for( i=0; i<2; i++ ) {
11004             int idx = oob[i];
11005
11006             if( idx >= 0 ) {
11007                 if( i > 0 && oob[0] >= 0 ) {
11008                     strcat( buf, "   " );
11009                 }
11010
11011                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11012                 sprintf( buf+strlen(buf), "%s%.2f",
11013                     pvInfoList[idx].score >= 0 ? "+" : "",
11014                     pvInfoList[idx].score / 100.0 );
11015             }
11016         }
11017     }
11018 }
11019
11020 /* Save game in PGN style and close the file */
11021 int
11022 SaveGamePGN(f)
11023      FILE *f;
11024 {
11025     int i, offset, linelen, newblock;
11026     time_t tm;
11027 //    char *movetext;
11028     char numtext[32];
11029     int movelen, numlen, blank;
11030     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11031
11032     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11033
11034     tm = time((time_t *) NULL);
11035
11036     PrintPGNTags(f, &gameInfo);
11037
11038     if (backwardMostMove > 0 || startedFromSetupPosition) {
11039         char *fen = PositionToFEN(backwardMostMove, NULL);
11040         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11041         fprintf(f, "\n{--------------\n");
11042         PrintPosition(f, backwardMostMove);
11043         fprintf(f, "--------------}\n");
11044         free(fen);
11045     }
11046     else {
11047         /* [AS] Out of book annotation */
11048         if( appData.saveOutOfBookInfo ) {
11049             char buf[64];
11050
11051             GetOutOfBookInfo( buf );
11052
11053             if( buf[0] != '\0' ) {
11054                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11055             }
11056         }
11057
11058         fprintf(f, "\n");
11059     }
11060
11061     i = backwardMostMove;
11062     linelen = 0;
11063     newblock = TRUE;
11064
11065     while (i < forwardMostMove) {
11066         /* Print comments preceding this move */
11067         if (commentList[i] != NULL) {
11068             if (linelen > 0) fprintf(f, "\n");
11069             fprintf(f, "%s", commentList[i]);
11070             linelen = 0;
11071             newblock = TRUE;
11072         }
11073
11074         /* Format move number */
11075         if ((i % 2) == 0)
11076           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11077         else
11078           if (newblock)
11079             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11080           else
11081             numtext[0] = NULLCHAR;
11082
11083         numlen = strlen(numtext);
11084         newblock = FALSE;
11085
11086         /* Print move number */
11087         blank = linelen > 0 && numlen > 0;
11088         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11089             fprintf(f, "\n");
11090             linelen = 0;
11091             blank = 0;
11092         }
11093         if (blank) {
11094             fprintf(f, " ");
11095             linelen++;
11096         }
11097         fprintf(f, "%s", numtext);
11098         linelen += numlen;
11099
11100         /* Get move */
11101         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11102         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11103
11104         /* Print move */
11105         blank = linelen > 0 && movelen > 0;
11106         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11107             fprintf(f, "\n");
11108             linelen = 0;
11109             blank = 0;
11110         }
11111         if (blank) {
11112             fprintf(f, " ");
11113             linelen++;
11114         }
11115         fprintf(f, "%s", move_buffer);
11116         linelen += movelen;
11117
11118         /* [AS] Add PV info if present */
11119         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11120             /* [HGM] add time */
11121             char buf[MSG_SIZ]; int seconds;
11122
11123             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11124
11125             if( seconds <= 0)
11126               buf[0] = 0;
11127             else
11128               if( seconds < 30 )
11129                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11130               else
11131                 {
11132                   seconds = (seconds + 4)/10; // round to full seconds
11133                   if( seconds < 60 )
11134                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11135                   else
11136                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11137                 }
11138
11139             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11140                       pvInfoList[i].score >= 0 ? "+" : "",
11141                       pvInfoList[i].score / 100.0,
11142                       pvInfoList[i].depth,
11143                       buf );
11144
11145             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11146
11147             /* Print score/depth */
11148             blank = linelen > 0 && movelen > 0;
11149             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11150                 fprintf(f, "\n");
11151                 linelen = 0;
11152                 blank = 0;
11153             }
11154             if (blank) {
11155                 fprintf(f, " ");
11156                 linelen++;
11157             }
11158             fprintf(f, "%s", move_buffer);
11159             linelen += movelen;
11160         }
11161
11162         i++;
11163     }
11164
11165     /* Start a new line */
11166     if (linelen > 0) fprintf(f, "\n");
11167
11168     /* Print comments after last move */
11169     if (commentList[i] != NULL) {
11170         fprintf(f, "%s\n", commentList[i]);
11171     }
11172
11173     /* Print result */
11174     if (gameInfo.resultDetails != NULL &&
11175         gameInfo.resultDetails[0] != NULLCHAR) {
11176         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11177                 PGNResult(gameInfo.result));
11178     } else {
11179         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11180     }
11181
11182     fclose(f);
11183     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11184     return TRUE;
11185 }
11186
11187 /* Save game in old style and close the file */
11188 int
11189 SaveGameOldStyle(f)
11190      FILE *f;
11191 {
11192     int i, offset;
11193     time_t tm;
11194
11195     tm = time((time_t *) NULL);
11196
11197     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11198     PrintOpponents(f);
11199
11200     if (backwardMostMove > 0 || startedFromSetupPosition) {
11201         fprintf(f, "\n[--------------\n");
11202         PrintPosition(f, backwardMostMove);
11203         fprintf(f, "--------------]\n");
11204     } else {
11205         fprintf(f, "\n");
11206     }
11207
11208     i = backwardMostMove;
11209     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11210
11211     while (i < forwardMostMove) {
11212         if (commentList[i] != NULL) {
11213             fprintf(f, "[%s]\n", commentList[i]);
11214         }
11215
11216         if ((i % 2) == 1) {
11217             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11218             i++;
11219         } else {
11220             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11221             i++;
11222             if (commentList[i] != NULL) {
11223                 fprintf(f, "\n");
11224                 continue;
11225             }
11226             if (i >= forwardMostMove) {
11227                 fprintf(f, "\n");
11228                 break;
11229             }
11230             fprintf(f, "%s\n", parseList[i]);
11231             i++;
11232         }
11233     }
11234
11235     if (commentList[i] != NULL) {
11236         fprintf(f, "[%s]\n", commentList[i]);
11237     }
11238
11239     /* This isn't really the old style, but it's close enough */
11240     if (gameInfo.resultDetails != NULL &&
11241         gameInfo.resultDetails[0] != NULLCHAR) {
11242         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11243                 gameInfo.resultDetails);
11244     } else {
11245         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11246     }
11247
11248     fclose(f);
11249     return TRUE;
11250 }
11251
11252 /* Save the current game to open file f and close the file */
11253 int
11254 SaveGame(f, dummy, dummy2)
11255      FILE *f;
11256      int dummy;
11257      char *dummy2;
11258 {
11259     if (gameMode == EditPosition) EditPositionDone(TRUE);
11260     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11261     if (appData.oldSaveStyle)
11262       return SaveGameOldStyle(f);
11263     else
11264       return SaveGamePGN(f);
11265 }
11266
11267 /* Save the current position to the given file */
11268 int
11269 SavePositionToFile(filename)
11270      char *filename;
11271 {
11272     FILE *f;
11273     char buf[MSG_SIZ];
11274
11275     if (strcmp(filename, "-") == 0) {
11276         return SavePosition(stdout, 0, NULL);
11277     } else {
11278         f = fopen(filename, "a");
11279         if (f == NULL) {
11280             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11281             DisplayError(buf, errno);
11282             return FALSE;
11283         } else {
11284             SavePosition(f, 0, NULL);
11285             return TRUE;
11286         }
11287     }
11288 }
11289
11290 /* Save the current position to the given open file and close the file */
11291 int
11292 SavePosition(f, dummy, dummy2)
11293      FILE *f;
11294      int dummy;
11295      char *dummy2;
11296 {
11297     time_t tm;
11298     char *fen;
11299
11300     if (gameMode == EditPosition) EditPositionDone(TRUE);
11301     if (appData.oldSaveStyle) {
11302         tm = time((time_t *) NULL);
11303
11304         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11305         PrintOpponents(f);
11306         fprintf(f, "[--------------\n");
11307         PrintPosition(f, currentMove);
11308         fprintf(f, "--------------]\n");
11309     } else {
11310         fen = PositionToFEN(currentMove, NULL);
11311         fprintf(f, "%s\n", fen);
11312         free(fen);
11313     }
11314     fclose(f);
11315     return TRUE;
11316 }
11317
11318 void
11319 ReloadCmailMsgEvent(unregister)
11320      int unregister;
11321 {
11322 #if !WIN32
11323     static char *inFilename = NULL;
11324     static char *outFilename;
11325     int i;
11326     struct stat inbuf, outbuf;
11327     int status;
11328
11329     /* Any registered moves are unregistered if unregister is set, */
11330     /* i.e. invoked by the signal handler */
11331     if (unregister) {
11332         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11333             cmailMoveRegistered[i] = FALSE;
11334             if (cmailCommentList[i] != NULL) {
11335                 free(cmailCommentList[i]);
11336                 cmailCommentList[i] = NULL;
11337             }
11338         }
11339         nCmailMovesRegistered = 0;
11340     }
11341
11342     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11343         cmailResult[i] = CMAIL_NOT_RESULT;
11344     }
11345     nCmailResults = 0;
11346
11347     if (inFilename == NULL) {
11348         /* Because the filenames are static they only get malloced once  */
11349         /* and they never get freed                                      */
11350         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11351         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11352
11353         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11354         sprintf(outFilename, "%s.out", appData.cmailGameName);
11355     }
11356
11357     status = stat(outFilename, &outbuf);
11358     if (status < 0) {
11359         cmailMailedMove = FALSE;
11360     } else {
11361         status = stat(inFilename, &inbuf);
11362         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11363     }
11364
11365     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11366        counts the games, notes how each one terminated, etc.
11367
11368        It would be nice to remove this kludge and instead gather all
11369        the information while building the game list.  (And to keep it
11370        in the game list nodes instead of having a bunch of fixed-size
11371        parallel arrays.)  Note this will require getting each game's
11372        termination from the PGN tags, as the game list builder does
11373        not process the game moves.  --mann
11374        */
11375     cmailMsgLoaded = TRUE;
11376     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11377
11378     /* Load first game in the file or popup game menu */
11379     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11380
11381 #endif /* !WIN32 */
11382     return;
11383 }
11384
11385 int
11386 RegisterMove()
11387 {
11388     FILE *f;
11389     char string[MSG_SIZ];
11390
11391     if (   cmailMailedMove
11392         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11393         return TRUE;            /* Allow free viewing  */
11394     }
11395
11396     /* Unregister move to ensure that we don't leave RegisterMove        */
11397     /* with the move registered when the conditions for registering no   */
11398     /* longer hold                                                       */
11399     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11400         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11401         nCmailMovesRegistered --;
11402
11403         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11404           {
11405               free(cmailCommentList[lastLoadGameNumber - 1]);
11406               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11407           }
11408     }
11409
11410     if (cmailOldMove == -1) {
11411         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11412         return FALSE;
11413     }
11414
11415     if (currentMove > cmailOldMove + 1) {
11416         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11417         return FALSE;
11418     }
11419
11420     if (currentMove < cmailOldMove) {
11421         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11422         return FALSE;
11423     }
11424
11425     if (forwardMostMove > currentMove) {
11426         /* Silently truncate extra moves */
11427         TruncateGame();
11428     }
11429
11430     if (   (currentMove == cmailOldMove + 1)
11431         || (   (currentMove == cmailOldMove)
11432             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11433                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11434         if (gameInfo.result != GameUnfinished) {
11435             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11436         }
11437
11438         if (commentList[currentMove] != NULL) {
11439             cmailCommentList[lastLoadGameNumber - 1]
11440               = StrSave(commentList[currentMove]);
11441         }
11442         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11443
11444         if (appData.debugMode)
11445           fprintf(debugFP, "Saving %s for game %d\n",
11446                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11447
11448         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11449
11450         f = fopen(string, "w");
11451         if (appData.oldSaveStyle) {
11452             SaveGameOldStyle(f); /* also closes the file */
11453
11454             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11455             f = fopen(string, "w");
11456             SavePosition(f, 0, NULL); /* also closes the file */
11457         } else {
11458             fprintf(f, "{--------------\n");
11459             PrintPosition(f, currentMove);
11460             fprintf(f, "--------------}\n\n");
11461
11462             SaveGame(f, 0, NULL); /* also closes the file*/
11463         }
11464
11465         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11466         nCmailMovesRegistered ++;
11467     } else if (nCmailGames == 1) {
11468         DisplayError(_("You have not made a move yet"), 0);
11469         return FALSE;
11470     }
11471
11472     return TRUE;
11473 }
11474
11475 void
11476 MailMoveEvent()
11477 {
11478 #if !WIN32
11479     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11480     FILE *commandOutput;
11481     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11482     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11483     int nBuffers;
11484     int i;
11485     int archived;
11486     char *arcDir;
11487
11488     if (! cmailMsgLoaded) {
11489         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11490         return;
11491     }
11492
11493     if (nCmailGames == nCmailResults) {
11494         DisplayError(_("No unfinished games"), 0);
11495         return;
11496     }
11497
11498 #if CMAIL_PROHIBIT_REMAIL
11499     if (cmailMailedMove) {
11500       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);
11501         DisplayError(msg, 0);
11502         return;
11503     }
11504 #endif
11505
11506     if (! (cmailMailedMove || RegisterMove())) return;
11507
11508     if (   cmailMailedMove
11509         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11510       snprintf(string, MSG_SIZ, partCommandString,
11511                appData.debugMode ? " -v" : "", appData.cmailGameName);
11512         commandOutput = popen(string, "r");
11513
11514         if (commandOutput == NULL) {
11515             DisplayError(_("Failed to invoke cmail"), 0);
11516         } else {
11517             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11518                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11519             }
11520             if (nBuffers > 1) {
11521                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11522                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11523                 nBytes = MSG_SIZ - 1;
11524             } else {
11525                 (void) memcpy(msg, buffer, nBytes);
11526             }
11527             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11528
11529             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11530                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11531
11532                 archived = TRUE;
11533                 for (i = 0; i < nCmailGames; i ++) {
11534                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11535                         archived = FALSE;
11536                     }
11537                 }
11538                 if (   archived
11539                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11540                         != NULL)) {
11541                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11542                            arcDir,
11543                            appData.cmailGameName,
11544                            gameInfo.date);
11545                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11546                     cmailMsgLoaded = FALSE;
11547                 }
11548             }
11549
11550             DisplayInformation(msg);
11551             pclose(commandOutput);
11552         }
11553     } else {
11554         if ((*cmailMsg) != '\0') {
11555             DisplayInformation(cmailMsg);
11556         }
11557     }
11558
11559     return;
11560 #endif /* !WIN32 */
11561 }
11562
11563 char *
11564 CmailMsg()
11565 {
11566 #if WIN32
11567     return NULL;
11568 #else
11569     int  prependComma = 0;
11570     char number[5];
11571     char string[MSG_SIZ];       /* Space for game-list */
11572     int  i;
11573
11574     if (!cmailMsgLoaded) return "";
11575
11576     if (cmailMailedMove) {
11577       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11578     } else {
11579         /* Create a list of games left */
11580       snprintf(string, MSG_SIZ, "[");
11581         for (i = 0; i < nCmailGames; i ++) {
11582             if (! (   cmailMoveRegistered[i]
11583                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11584                 if (prependComma) {
11585                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11586                 } else {
11587                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11588                     prependComma = 1;
11589                 }
11590
11591                 strcat(string, number);
11592             }
11593         }
11594         strcat(string, "]");
11595
11596         if (nCmailMovesRegistered + nCmailResults == 0) {
11597             switch (nCmailGames) {
11598               case 1:
11599                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11600                 break;
11601
11602               case 2:
11603                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11604                 break;
11605
11606               default:
11607                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11608                          nCmailGames);
11609                 break;
11610             }
11611         } else {
11612             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11613               case 1:
11614                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11615                          string);
11616                 break;
11617
11618               case 0:
11619                 if (nCmailResults == nCmailGames) {
11620                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11621                 } else {
11622                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11623                 }
11624                 break;
11625
11626               default:
11627                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11628                          string);
11629             }
11630         }
11631     }
11632     return cmailMsg;
11633 #endif /* WIN32 */
11634 }
11635
11636 void
11637 ResetGameEvent()
11638 {
11639     if (gameMode == Training)
11640       SetTrainingModeOff();
11641
11642     Reset(TRUE, TRUE);
11643     cmailMsgLoaded = FALSE;
11644     if (appData.icsActive) {
11645       SendToICS(ics_prefix);
11646       SendToICS("refresh\n");
11647     }
11648 }
11649
11650 void
11651 ExitEvent(status)
11652      int status;
11653 {
11654     exiting++;
11655     if (exiting > 2) {
11656       /* Give up on clean exit */
11657       exit(status);
11658     }
11659     if (exiting > 1) {
11660       /* Keep trying for clean exit */
11661       return;
11662     }
11663
11664     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11665
11666     if (telnetISR != NULL) {
11667       RemoveInputSource(telnetISR);
11668     }
11669     if (icsPR != NoProc) {
11670       DestroyChildProcess(icsPR, TRUE);
11671     }
11672
11673     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11674     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11675
11676     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11677     /* make sure this other one finishes before killing it!                  */
11678     if(endingGame) { int count = 0;
11679         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11680         while(endingGame && count++ < 10) DoSleep(1);
11681         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11682     }
11683
11684     /* Kill off chess programs */
11685     if (first.pr != NoProc) {
11686         ExitAnalyzeMode();
11687
11688         DoSleep( appData.delayBeforeQuit );
11689         SendToProgram("quit\n", &first);
11690         DoSleep( appData.delayAfterQuit );
11691         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11692     }
11693     if (second.pr != NoProc) {
11694         DoSleep( appData.delayBeforeQuit );
11695         SendToProgram("quit\n", &second);
11696         DoSleep( appData.delayAfterQuit );
11697         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11698     }
11699     if (first.isr != NULL) {
11700         RemoveInputSource(first.isr);
11701     }
11702     if (second.isr != NULL) {
11703         RemoveInputSource(second.isr);
11704     }
11705
11706     ShutDownFrontEnd();
11707     exit(status);
11708 }
11709
11710 void
11711 PauseEvent()
11712 {
11713     if (appData.debugMode)
11714         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11715     if (pausing) {
11716         pausing = FALSE;
11717         ModeHighlight();
11718         if (gameMode == MachinePlaysWhite ||
11719             gameMode == MachinePlaysBlack) {
11720             StartClocks();
11721         } else {
11722             DisplayBothClocks();
11723         }
11724         if (gameMode == PlayFromGameFile) {
11725             if (appData.timeDelay >= 0)
11726                 AutoPlayGameLoop();
11727         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11728             Reset(FALSE, TRUE);
11729             SendToICS(ics_prefix);
11730             SendToICS("refresh\n");
11731         } else if (currentMove < forwardMostMove) {
11732             ForwardInner(forwardMostMove);
11733         }
11734         pauseExamInvalid = FALSE;
11735     } else {
11736         switch (gameMode) {
11737           default:
11738             return;
11739           case IcsExamining:
11740             pauseExamForwardMostMove = forwardMostMove;
11741             pauseExamInvalid = FALSE;
11742             /* fall through */
11743           case IcsObserving:
11744           case IcsPlayingWhite:
11745           case IcsPlayingBlack:
11746             pausing = TRUE;
11747             ModeHighlight();
11748             return;
11749           case PlayFromGameFile:
11750             (void) StopLoadGameTimer();
11751             pausing = TRUE;
11752             ModeHighlight();
11753             break;
11754           case BeginningOfGame:
11755             if (appData.icsActive) return;
11756             /* else fall through */
11757           case MachinePlaysWhite:
11758           case MachinePlaysBlack:
11759           case TwoMachinesPlay:
11760             if (forwardMostMove == 0)
11761               return;           /* don't pause if no one has moved */
11762             if ((gameMode == MachinePlaysWhite &&
11763                  !WhiteOnMove(forwardMostMove)) ||
11764                 (gameMode == MachinePlaysBlack &&
11765                  WhiteOnMove(forwardMostMove))) {
11766                 StopClocks();
11767             }
11768             pausing = TRUE;
11769             ModeHighlight();
11770             break;
11771         }
11772     }
11773 }
11774
11775 void
11776 EditCommentEvent()
11777 {
11778     char title[MSG_SIZ];
11779
11780     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11781       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11782     } else {
11783       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11784                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11785                parseList[currentMove - 1]);
11786     }
11787
11788     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11789 }
11790
11791
11792 void
11793 EditTagsEvent()
11794 {
11795     char *tags = PGNTags(&gameInfo);
11796     EditTagsPopUp(tags, NULL);
11797     free(tags);
11798 }
11799
11800 void
11801 AnalyzeModeEvent()
11802 {
11803     if (appData.noChessProgram || gameMode == AnalyzeMode)
11804       return;
11805
11806     if (gameMode != AnalyzeFile) {
11807         if (!appData.icsEngineAnalyze) {
11808                EditGameEvent();
11809                if (gameMode != EditGame) return;
11810         }
11811         ResurrectChessProgram();
11812         SendToProgram("analyze\n", &first);
11813         first.analyzing = TRUE;
11814         /*first.maybeThinking = TRUE;*/
11815         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11816         EngineOutputPopUp();
11817     }
11818     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11819     pausing = FALSE;
11820     ModeHighlight();
11821     SetGameInfo();
11822
11823     StartAnalysisClock();
11824     GetTimeMark(&lastNodeCountTime);
11825     lastNodeCount = 0;
11826 }
11827
11828 void
11829 AnalyzeFileEvent()
11830 {
11831     if (appData.noChessProgram || gameMode == AnalyzeFile)
11832       return;
11833
11834     if (gameMode != AnalyzeMode) {
11835         EditGameEvent();
11836         if (gameMode != EditGame) return;
11837         ResurrectChessProgram();
11838         SendToProgram("analyze\n", &first);
11839         first.analyzing = TRUE;
11840         /*first.maybeThinking = TRUE;*/
11841         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11842         EngineOutputPopUp();
11843     }
11844     gameMode = AnalyzeFile;
11845     pausing = FALSE;
11846     ModeHighlight();
11847     SetGameInfo();
11848
11849     StartAnalysisClock();
11850     GetTimeMark(&lastNodeCountTime);
11851     lastNodeCount = 0;
11852 }
11853
11854 void
11855 MachineWhiteEvent()
11856 {
11857     char buf[MSG_SIZ];
11858     char *bookHit = NULL;
11859
11860     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11861       return;
11862
11863
11864     if (gameMode == PlayFromGameFile ||
11865         gameMode == TwoMachinesPlay  ||
11866         gameMode == Training         ||
11867         gameMode == AnalyzeMode      ||
11868         gameMode == EndOfGame)
11869         EditGameEvent();
11870
11871     if (gameMode == EditPosition)
11872         EditPositionDone(TRUE);
11873
11874     if (!WhiteOnMove(currentMove)) {
11875         DisplayError(_("It is not White's turn"), 0);
11876         return;
11877     }
11878
11879     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11880       ExitAnalyzeMode();
11881
11882     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11883         gameMode == AnalyzeFile)
11884         TruncateGame();
11885
11886     ResurrectChessProgram();    /* in case it isn't running */
11887     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11888         gameMode = MachinePlaysWhite;
11889         ResetClocks();
11890     } else
11891     gameMode = MachinePlaysWhite;
11892     pausing = FALSE;
11893     ModeHighlight();
11894     SetGameInfo();
11895     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11896     DisplayTitle(buf);
11897     if (first.sendName) {
11898       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11899       SendToProgram(buf, &first);
11900     }
11901     if (first.sendTime) {
11902       if (first.useColors) {
11903         SendToProgram("black\n", &first); /*gnu kludge*/
11904       }
11905       SendTimeRemaining(&first, TRUE);
11906     }
11907     if (first.useColors) {
11908       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11909     }
11910     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11911     SetMachineThinkingEnables();
11912     first.maybeThinking = TRUE;
11913     StartClocks();
11914     firstMove = FALSE;
11915
11916     if (appData.autoFlipView && !flipView) {
11917       flipView = !flipView;
11918       DrawPosition(FALSE, NULL);
11919       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11920     }
11921
11922     if(bookHit) { // [HGM] book: simulate book reply
11923         static char bookMove[MSG_SIZ]; // a bit generous?
11924
11925         programStats.nodes = programStats.depth = programStats.time =
11926         programStats.score = programStats.got_only_move = 0;
11927         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11928
11929         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11930         strcat(bookMove, bookHit);
11931         HandleMachineMove(bookMove, &first);
11932     }
11933 }
11934
11935 void
11936 MachineBlackEvent()
11937 {
11938   char buf[MSG_SIZ];
11939   char *bookHit = NULL;
11940
11941     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11942         return;
11943
11944
11945     if (gameMode == PlayFromGameFile ||
11946         gameMode == TwoMachinesPlay  ||
11947         gameMode == Training         ||
11948         gameMode == AnalyzeMode      ||
11949         gameMode == EndOfGame)
11950         EditGameEvent();
11951
11952     if (gameMode == EditPosition)
11953         EditPositionDone(TRUE);
11954
11955     if (WhiteOnMove(currentMove)) {
11956         DisplayError(_("It is not Black's turn"), 0);
11957         return;
11958     }
11959
11960     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11961       ExitAnalyzeMode();
11962
11963     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11964         gameMode == AnalyzeFile)
11965         TruncateGame();
11966
11967     ResurrectChessProgram();    /* in case it isn't running */
11968     gameMode = MachinePlaysBlack;
11969     pausing = FALSE;
11970     ModeHighlight();
11971     SetGameInfo();
11972     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11973     DisplayTitle(buf);
11974     if (first.sendName) {
11975       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11976       SendToProgram(buf, &first);
11977     }
11978     if (first.sendTime) {
11979       if (first.useColors) {
11980         SendToProgram("white\n", &first); /*gnu kludge*/
11981       }
11982       SendTimeRemaining(&first, FALSE);
11983     }
11984     if (first.useColors) {
11985       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11986     }
11987     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11988     SetMachineThinkingEnables();
11989     first.maybeThinking = TRUE;
11990     StartClocks();
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     if(bookHit) { // [HGM] book: simulate book reply
11998         static char bookMove[MSG_SIZ]; // a bit generous?
11999
12000         programStats.nodes = programStats.depth = programStats.time =
12001         programStats.score = programStats.got_only_move = 0;
12002         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12003
12004         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12005         strcat(bookMove, bookHit);
12006         HandleMachineMove(bookMove, &first);
12007     }
12008 }
12009
12010
12011 void
12012 DisplayTwoMachinesTitle()
12013 {
12014     char buf[MSG_SIZ];
12015     if (appData.matchGames > 0) {
12016         if (first.twoMachinesColor[0] == 'w') {
12017           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12018                    gameInfo.white, gameInfo.black,
12019                    first.matchWins, second.matchWins,
12020                    matchGame - 1 - (first.matchWins + second.matchWins));
12021         } else {
12022           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12023                    gameInfo.white, gameInfo.black,
12024                    second.matchWins, first.matchWins,
12025                    matchGame - 1 - (first.matchWins + second.matchWins));
12026         }
12027     } else {
12028       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12029     }
12030     DisplayTitle(buf);
12031 }
12032
12033 void
12034 SettingsMenuIfReady()
12035 {
12036   if (second.lastPing != second.lastPong) {
12037     DisplayMessage("", _("Waiting for second chess program"));
12038     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12039     return;
12040   }
12041   ThawUI();
12042   DisplayMessage("", "");
12043   SettingsPopUp(&second);
12044 }
12045
12046 int
12047 WaitForSecond(DelayedEventCallback retry)
12048 {
12049     if (second.pr == NULL) {
12050         StartChessProgram(&second);
12051         if (second.protocolVersion == 1) {
12052           retry();
12053         } else {
12054           /* kludge: allow timeout for initial "feature" command */
12055           FreezeUI();
12056           DisplayMessage("", _("Starting second chess program"));
12057           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12058         }
12059         return 1;
12060     }
12061     return 0;
12062 }
12063
12064 void
12065 TwoMachinesEvent P((void))
12066 {
12067     int i;
12068     char buf[MSG_SIZ];
12069     ChessProgramState *onmove;
12070     char *bookHit = NULL;
12071
12072     if (appData.noChessProgram) return;
12073
12074     switch (gameMode) {
12075       case TwoMachinesPlay:
12076         return;
12077       case MachinePlaysWhite:
12078       case MachinePlaysBlack:
12079         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12080             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12081             return;
12082         }
12083         /* fall through */
12084       case BeginningOfGame:
12085       case PlayFromGameFile:
12086       case EndOfGame:
12087         EditGameEvent();
12088         if (gameMode != EditGame) return;
12089         break;
12090       case EditPosition:
12091         EditPositionDone(TRUE);
12092         break;
12093       case AnalyzeMode:
12094       case AnalyzeFile:
12095         ExitAnalyzeMode();
12096         break;
12097       case EditGame:
12098       default:
12099         break;
12100     }
12101
12102 //    forwardMostMove = currentMove;
12103     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12104     ResurrectChessProgram();    /* in case first program isn't running */
12105
12106     if(WaitForSecond(TwoMachinesEventIfReady)) return;
12107     DisplayMessage("", "");
12108     InitChessProgram(&second, FALSE);
12109     SendToProgram("force\n", &second);
12110     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12111       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12112       return;
12113     }
12114     if (startedFromSetupPosition) {
12115         SendBoard(&second, backwardMostMove);
12116     if (appData.debugMode) {
12117         fprintf(debugFP, "Two Machines\n");
12118     }
12119     }
12120     for (i = backwardMostMove; i < forwardMostMove; i++) {
12121         SendMoveToProgram(i, &second);
12122     }
12123
12124     gameMode = TwoMachinesPlay;
12125     pausing = FALSE;
12126     ModeHighlight();
12127     SetGameInfo();
12128     DisplayTwoMachinesTitle();
12129     firstMove = TRUE;
12130     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12131         onmove = &first;
12132     } else {
12133         onmove = &second;
12134     }
12135
12136     SendToProgram(first.computerString, &first);
12137     if (first.sendName) {
12138       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12139       SendToProgram(buf, &first);
12140     }
12141     SendToProgram(second.computerString, &second);
12142     if (second.sendName) {
12143       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12144       SendToProgram(buf, &second);
12145     }
12146
12147     ResetClocks();
12148     if (!first.sendTime || !second.sendTime) {
12149         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12150         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12151     }
12152     if (onmove->sendTime) {
12153       if (onmove->useColors) {
12154         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12155       }
12156       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12157     }
12158     if (onmove->useColors) {
12159       SendToProgram(onmove->twoMachinesColor, onmove);
12160     }
12161     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12162 //    SendToProgram("go\n", onmove);
12163     onmove->maybeThinking = TRUE;
12164     SetMachineThinkingEnables();
12165
12166     StartClocks();
12167
12168     if(bookHit) { // [HGM] book: simulate book reply
12169         static char bookMove[MSG_SIZ]; // a bit generous?
12170
12171         programStats.nodes = programStats.depth = programStats.time =
12172         programStats.score = programStats.got_only_move = 0;
12173         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12174
12175         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12176         strcat(bookMove, bookHit);
12177         savedMessage = bookMove; // args for deferred call
12178         savedState = onmove;
12179         ScheduleDelayedEvent(DeferredBookMove, 1);
12180     }
12181 }
12182
12183 void
12184 TrainingEvent()
12185 {
12186     if (gameMode == Training) {
12187       SetTrainingModeOff();
12188       gameMode = PlayFromGameFile;
12189       DisplayMessage("", _("Training mode off"));
12190     } else {
12191       gameMode = Training;
12192       animateTraining = appData.animate;
12193
12194       /* make sure we are not already at the end of the game */
12195       if (currentMove < forwardMostMove) {
12196         SetTrainingModeOn();
12197         DisplayMessage("", _("Training mode on"));
12198       } else {
12199         gameMode = PlayFromGameFile;
12200         DisplayError(_("Already at end of game"), 0);
12201       }
12202     }
12203     ModeHighlight();
12204 }
12205
12206 void
12207 IcsClientEvent()
12208 {
12209     if (!appData.icsActive) return;
12210     switch (gameMode) {
12211       case IcsPlayingWhite:
12212       case IcsPlayingBlack:
12213       case IcsObserving:
12214       case IcsIdle:
12215       case BeginningOfGame:
12216       case IcsExamining:
12217         return;
12218
12219       case EditGame:
12220         break;
12221
12222       case EditPosition:
12223         EditPositionDone(TRUE);
12224         break;
12225
12226       case AnalyzeMode:
12227       case AnalyzeFile:
12228         ExitAnalyzeMode();
12229         break;
12230
12231       default:
12232         EditGameEvent();
12233         break;
12234     }
12235
12236     gameMode = IcsIdle;
12237     ModeHighlight();
12238     return;
12239 }
12240
12241
12242 void
12243 EditGameEvent()
12244 {
12245     int i;
12246
12247     switch (gameMode) {
12248       case Training:
12249         SetTrainingModeOff();
12250         break;
12251       case MachinePlaysWhite:
12252       case MachinePlaysBlack:
12253       case BeginningOfGame:
12254         SendToProgram("force\n", &first);
12255         SetUserThinkingEnables();
12256         break;
12257       case PlayFromGameFile:
12258         (void) StopLoadGameTimer();
12259         if (gameFileFP != NULL) {
12260             gameFileFP = NULL;
12261         }
12262         break;
12263       case EditPosition:
12264         EditPositionDone(TRUE);
12265         break;
12266       case AnalyzeMode:
12267       case AnalyzeFile:
12268         ExitAnalyzeMode();
12269         SendToProgram("force\n", &first);
12270         break;
12271       case TwoMachinesPlay:
12272         GameEnds(EndOfFile, NULL, GE_PLAYER);
12273         ResurrectChessProgram();
12274         SetUserThinkingEnables();
12275         break;
12276       case EndOfGame:
12277         ResurrectChessProgram();
12278         break;
12279       case IcsPlayingBlack:
12280       case IcsPlayingWhite:
12281         DisplayError(_("Warning: You are still playing a game"), 0);
12282         break;
12283       case IcsObserving:
12284         DisplayError(_("Warning: You are still observing a game"), 0);
12285         break;
12286       case IcsExamining:
12287         DisplayError(_("Warning: You are still examining a game"), 0);
12288         break;
12289       case IcsIdle:
12290         break;
12291       case EditGame:
12292       default:
12293         return;
12294     }
12295
12296     pausing = FALSE;
12297     StopClocks();
12298     first.offeredDraw = second.offeredDraw = 0;
12299
12300     if (gameMode == PlayFromGameFile) {
12301         whiteTimeRemaining = timeRemaining[0][currentMove];
12302         blackTimeRemaining = timeRemaining[1][currentMove];
12303         DisplayTitle("");
12304     }
12305
12306     if (gameMode == MachinePlaysWhite ||
12307         gameMode == MachinePlaysBlack ||
12308         gameMode == TwoMachinesPlay ||
12309         gameMode == EndOfGame) {
12310         i = forwardMostMove;
12311         while (i > currentMove) {
12312             SendToProgram("undo\n", &first);
12313             i--;
12314         }
12315         whiteTimeRemaining = timeRemaining[0][currentMove];
12316         blackTimeRemaining = timeRemaining[1][currentMove];
12317         DisplayBothClocks();
12318         if (whiteFlag || blackFlag) {
12319             whiteFlag = blackFlag = 0;
12320         }
12321         DisplayTitle("");
12322     }
12323
12324     gameMode = EditGame;
12325     ModeHighlight();
12326     SetGameInfo();
12327 }
12328
12329
12330 void
12331 EditPositionEvent()
12332 {
12333     if (gameMode == EditPosition) {
12334         EditGameEvent();
12335         return;
12336     }
12337
12338     EditGameEvent();
12339     if (gameMode != EditGame) return;
12340
12341     gameMode = EditPosition;
12342     ModeHighlight();
12343     SetGameInfo();
12344     if (currentMove > 0)
12345       CopyBoard(boards[0], boards[currentMove]);
12346
12347     blackPlaysFirst = !WhiteOnMove(currentMove);
12348     ResetClocks();
12349     currentMove = forwardMostMove = backwardMostMove = 0;
12350     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12351     DisplayMove(-1);
12352 }
12353
12354 void
12355 ExitAnalyzeMode()
12356 {
12357     /* [DM] icsEngineAnalyze - possible call from other functions */
12358     if (appData.icsEngineAnalyze) {
12359         appData.icsEngineAnalyze = FALSE;
12360
12361         DisplayMessage("",_("Close ICS engine analyze..."));
12362     }
12363     if (first.analysisSupport && first.analyzing) {
12364       SendToProgram("exit\n", &first);
12365       first.analyzing = FALSE;
12366     }
12367     thinkOutput[0] = NULLCHAR;
12368 }
12369
12370 void
12371 EditPositionDone(Boolean fakeRights)
12372 {
12373     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12374
12375     startedFromSetupPosition = TRUE;
12376     InitChessProgram(&first, FALSE);
12377     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12378       boards[0][EP_STATUS] = EP_NONE;
12379       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12380     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12381         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12382         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12383       } else boards[0][CASTLING][2] = NoRights;
12384     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12385         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12386         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12387       } else boards[0][CASTLING][5] = NoRights;
12388     }
12389     SendToProgram("force\n", &first);
12390     if (blackPlaysFirst) {
12391         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12392         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12393         currentMove = forwardMostMove = backwardMostMove = 1;
12394         CopyBoard(boards[1], boards[0]);
12395     } else {
12396         currentMove = forwardMostMove = backwardMostMove = 0;
12397     }
12398     SendBoard(&first, forwardMostMove);
12399     if (appData.debugMode) {
12400         fprintf(debugFP, "EditPosDone\n");
12401     }
12402     DisplayTitle("");
12403     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12404     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12405     gameMode = EditGame;
12406     ModeHighlight();
12407     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12408     ClearHighlights(); /* [AS] */
12409 }
12410
12411 /* Pause for `ms' milliseconds */
12412 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12413 void
12414 TimeDelay(ms)
12415      long ms;
12416 {
12417     TimeMark m1, m2;
12418
12419     GetTimeMark(&m1);
12420     do {
12421         GetTimeMark(&m2);
12422     } while (SubtractTimeMarks(&m2, &m1) < ms);
12423 }
12424
12425 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12426 void
12427 SendMultiLineToICS(buf)
12428      char *buf;
12429 {
12430     char temp[MSG_SIZ+1], *p;
12431     int len;
12432
12433     len = strlen(buf);
12434     if (len > MSG_SIZ)
12435       len = MSG_SIZ;
12436
12437     strncpy(temp, buf, len);
12438     temp[len] = 0;
12439
12440     p = temp;
12441     while (*p) {
12442         if (*p == '\n' || *p == '\r')
12443           *p = ' ';
12444         ++p;
12445     }
12446
12447     strcat(temp, "\n");
12448     SendToICS(temp);
12449     SendToPlayer(temp, strlen(temp));
12450 }
12451
12452 void
12453 SetWhiteToPlayEvent()
12454 {
12455     if (gameMode == EditPosition) {
12456         blackPlaysFirst = FALSE;
12457         DisplayBothClocks();    /* works because currentMove is 0 */
12458     } else if (gameMode == IcsExamining) {
12459         SendToICS(ics_prefix);
12460         SendToICS("tomove white\n");
12461     }
12462 }
12463
12464 void
12465 SetBlackToPlayEvent()
12466 {
12467     if (gameMode == EditPosition) {
12468         blackPlaysFirst = TRUE;
12469         currentMove = 1;        /* kludge */
12470         DisplayBothClocks();
12471         currentMove = 0;
12472     } else if (gameMode == IcsExamining) {
12473         SendToICS(ics_prefix);
12474         SendToICS("tomove black\n");
12475     }
12476 }
12477
12478 void
12479 EditPositionMenuEvent(selection, x, y)
12480      ChessSquare selection;
12481      int x, y;
12482 {
12483     char buf[MSG_SIZ];
12484     ChessSquare piece = boards[0][y][x];
12485
12486     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12487
12488     switch (selection) {
12489       case ClearBoard:
12490         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12491             SendToICS(ics_prefix);
12492             SendToICS("bsetup clear\n");
12493         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12494             SendToICS(ics_prefix);
12495             SendToICS("clearboard\n");
12496         } else {
12497             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12498                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12499                 for (y = 0; y < BOARD_HEIGHT; y++) {
12500                     if (gameMode == IcsExamining) {
12501                         if (boards[currentMove][y][x] != EmptySquare) {
12502                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12503                                     AAA + x, ONE + y);
12504                             SendToICS(buf);
12505                         }
12506                     } else {
12507                         boards[0][y][x] = p;
12508                     }
12509                 }
12510             }
12511         }
12512         if (gameMode == EditPosition) {
12513             DrawPosition(FALSE, boards[0]);
12514         }
12515         break;
12516
12517       case WhitePlay:
12518         SetWhiteToPlayEvent();
12519         break;
12520
12521       case BlackPlay:
12522         SetBlackToPlayEvent();
12523         break;
12524
12525       case EmptySquare:
12526         if (gameMode == IcsExamining) {
12527             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12528             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12529             SendToICS(buf);
12530         } else {
12531             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12532                 if(x == BOARD_LEFT-2) {
12533                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12534                     boards[0][y][1] = 0;
12535                 } else
12536                 if(x == BOARD_RGHT+1) {
12537                     if(y >= gameInfo.holdingsSize) break;
12538                     boards[0][y][BOARD_WIDTH-2] = 0;
12539                 } else break;
12540             }
12541             boards[0][y][x] = EmptySquare;
12542             DrawPosition(FALSE, boards[0]);
12543         }
12544         break;
12545
12546       case PromotePiece:
12547         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12548            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12549             selection = (ChessSquare) (PROMOTED piece);
12550         } else if(piece == EmptySquare) selection = WhiteSilver;
12551         else selection = (ChessSquare)((int)piece - 1);
12552         goto defaultlabel;
12553
12554       case DemotePiece:
12555         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12556            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12557             selection = (ChessSquare) (DEMOTED piece);
12558         } else if(piece == EmptySquare) selection = BlackSilver;
12559         else selection = (ChessSquare)((int)piece + 1);
12560         goto defaultlabel;
12561
12562       case WhiteQueen:
12563       case BlackQueen:
12564         if(gameInfo.variant == VariantShatranj ||
12565            gameInfo.variant == VariantXiangqi  ||
12566            gameInfo.variant == VariantCourier  ||
12567            gameInfo.variant == VariantMakruk     )
12568             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12569         goto defaultlabel;
12570
12571       case WhiteKing:
12572       case BlackKing:
12573         if(gameInfo.variant == VariantXiangqi)
12574             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12575         if(gameInfo.variant == VariantKnightmate)
12576             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12577       default:
12578         defaultlabel:
12579         if (gameMode == IcsExamining) {
12580             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12581             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12582                      PieceToChar(selection), AAA + x, ONE + y);
12583             SendToICS(buf);
12584         } else {
12585             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12586                 int n;
12587                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12588                     n = PieceToNumber(selection - BlackPawn);
12589                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12590                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12591                     boards[0][BOARD_HEIGHT-1-n][1]++;
12592                 } else
12593                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12594                     n = PieceToNumber(selection);
12595                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12596                     boards[0][n][BOARD_WIDTH-1] = selection;
12597                     boards[0][n][BOARD_WIDTH-2]++;
12598                 }
12599             } else
12600             boards[0][y][x] = selection;
12601             DrawPosition(TRUE, boards[0]);
12602         }
12603         break;
12604     }
12605 }
12606
12607
12608 void
12609 DropMenuEvent(selection, x, y)
12610      ChessSquare selection;
12611      int x, y;
12612 {
12613     ChessMove moveType;
12614
12615     switch (gameMode) {
12616       case IcsPlayingWhite:
12617       case MachinePlaysBlack:
12618         if (!WhiteOnMove(currentMove)) {
12619             DisplayMoveError(_("It is Black's turn"));
12620             return;
12621         }
12622         moveType = WhiteDrop;
12623         break;
12624       case IcsPlayingBlack:
12625       case MachinePlaysWhite:
12626         if (WhiteOnMove(currentMove)) {
12627             DisplayMoveError(_("It is White's turn"));
12628             return;
12629         }
12630         moveType = BlackDrop;
12631         break;
12632       case EditGame:
12633         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12634         break;
12635       default:
12636         return;
12637     }
12638
12639     if (moveType == BlackDrop && selection < BlackPawn) {
12640       selection = (ChessSquare) ((int) selection
12641                                  + (int) BlackPawn - (int) WhitePawn);
12642     }
12643     if (boards[currentMove][y][x] != EmptySquare) {
12644         DisplayMoveError(_("That square is occupied"));
12645         return;
12646     }
12647
12648     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12649 }
12650
12651 void
12652 AcceptEvent()
12653 {
12654     /* Accept a pending offer of any kind from opponent */
12655
12656     if (appData.icsActive) {
12657         SendToICS(ics_prefix);
12658         SendToICS("accept\n");
12659     } else if (cmailMsgLoaded) {
12660         if (currentMove == cmailOldMove &&
12661             commentList[cmailOldMove] != NULL &&
12662             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12663                    "Black offers a draw" : "White offers a draw")) {
12664             TruncateGame();
12665             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12666             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12667         } else {
12668             DisplayError(_("There is no pending offer on this move"), 0);
12669             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12670         }
12671     } else {
12672         /* Not used for offers from chess program */
12673     }
12674 }
12675
12676 void
12677 DeclineEvent()
12678 {
12679     /* Decline a pending offer of any kind from opponent */
12680
12681     if (appData.icsActive) {
12682         SendToICS(ics_prefix);
12683         SendToICS("decline\n");
12684     } else if (cmailMsgLoaded) {
12685         if (currentMove == cmailOldMove &&
12686             commentList[cmailOldMove] != NULL &&
12687             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12688                    "Black offers a draw" : "White offers a draw")) {
12689 #ifdef NOTDEF
12690             AppendComment(cmailOldMove, "Draw declined", TRUE);
12691             DisplayComment(cmailOldMove - 1, "Draw declined");
12692 #endif /*NOTDEF*/
12693         } else {
12694             DisplayError(_("There is no pending offer on this move"), 0);
12695         }
12696     } else {
12697         /* Not used for offers from chess program */
12698     }
12699 }
12700
12701 void
12702 RematchEvent()
12703 {
12704     /* Issue ICS rematch command */
12705     if (appData.icsActive) {
12706         SendToICS(ics_prefix);
12707         SendToICS("rematch\n");
12708     }
12709 }
12710
12711 void
12712 CallFlagEvent()
12713 {
12714     /* Call your opponent's flag (claim a win on time) */
12715     if (appData.icsActive) {
12716         SendToICS(ics_prefix);
12717         SendToICS("flag\n");
12718     } else {
12719         switch (gameMode) {
12720           default:
12721             return;
12722           case MachinePlaysWhite:
12723             if (whiteFlag) {
12724                 if (blackFlag)
12725                   GameEnds(GameIsDrawn, "Both players ran out of time",
12726                            GE_PLAYER);
12727                 else
12728                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12729             } else {
12730                 DisplayError(_("Your opponent is not out of time"), 0);
12731             }
12732             break;
12733           case MachinePlaysBlack:
12734             if (blackFlag) {
12735                 if (whiteFlag)
12736                   GameEnds(GameIsDrawn, "Both players ran out of time",
12737                            GE_PLAYER);
12738                 else
12739                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12740             } else {
12741                 DisplayError(_("Your opponent is not out of time"), 0);
12742             }
12743             break;
12744         }
12745     }
12746 }
12747
12748 void
12749 ClockClick(int which)
12750 {       // [HGM] code moved to back-end from winboard.c
12751         if(which) { // black clock
12752           if (gameMode == EditPosition || gameMode == IcsExamining) {
12753             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12754             SetBlackToPlayEvent();
12755           } else if (gameMode == EditGame || shiftKey) {
12756             AdjustClock(which, -1);
12757           } else if (gameMode == IcsPlayingWhite ||
12758                      gameMode == MachinePlaysBlack) {
12759             CallFlagEvent();
12760           }
12761         } else { // white clock
12762           if (gameMode == EditPosition || gameMode == IcsExamining) {
12763             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12764             SetWhiteToPlayEvent();
12765           } else if (gameMode == EditGame || shiftKey) {
12766             AdjustClock(which, -1);
12767           } else if (gameMode == IcsPlayingBlack ||
12768                    gameMode == MachinePlaysWhite) {
12769             CallFlagEvent();
12770           }
12771         }
12772 }
12773
12774 void
12775 DrawEvent()
12776 {
12777     /* Offer draw or accept pending draw offer from opponent */
12778
12779     if (appData.icsActive) {
12780         /* Note: tournament rules require draw offers to be
12781            made after you make your move but before you punch
12782            your clock.  Currently ICS doesn't let you do that;
12783            instead, you immediately punch your clock after making
12784            a move, but you can offer a draw at any time. */
12785
12786         SendToICS(ics_prefix);
12787         SendToICS("draw\n");
12788         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12789     } else if (cmailMsgLoaded) {
12790         if (currentMove == cmailOldMove &&
12791             commentList[cmailOldMove] != NULL &&
12792             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12793                    "Black offers a draw" : "White offers a draw")) {
12794             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12795             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12796         } else if (currentMove == cmailOldMove + 1) {
12797             char *offer = WhiteOnMove(cmailOldMove) ?
12798               "White offers a draw" : "Black offers a draw";
12799             AppendComment(currentMove, offer, TRUE);
12800             DisplayComment(currentMove - 1, offer);
12801             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12802         } else {
12803             DisplayError(_("You must make your move before offering a draw"), 0);
12804             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12805         }
12806     } else if (first.offeredDraw) {
12807         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12808     } else {
12809         if (first.sendDrawOffers) {
12810             SendToProgram("draw\n", &first);
12811             userOfferedDraw = TRUE;
12812         }
12813     }
12814 }
12815
12816 void
12817 AdjournEvent()
12818 {
12819     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12820
12821     if (appData.icsActive) {
12822         SendToICS(ics_prefix);
12823         SendToICS("adjourn\n");
12824     } else {
12825         /* Currently GNU Chess doesn't offer or accept Adjourns */
12826     }
12827 }
12828
12829
12830 void
12831 AbortEvent()
12832 {
12833     /* Offer Abort or accept pending Abort offer from opponent */
12834
12835     if (appData.icsActive) {
12836         SendToICS(ics_prefix);
12837         SendToICS("abort\n");
12838     } else {
12839         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12840     }
12841 }
12842
12843 void
12844 ResignEvent()
12845 {
12846     /* Resign.  You can do this even if it's not your turn. */
12847
12848     if (appData.icsActive) {
12849         SendToICS(ics_prefix);
12850         SendToICS("resign\n");
12851     } else {
12852         switch (gameMode) {
12853           case MachinePlaysWhite:
12854             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12855             break;
12856           case MachinePlaysBlack:
12857             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12858             break;
12859           case EditGame:
12860             if (cmailMsgLoaded) {
12861                 TruncateGame();
12862                 if (WhiteOnMove(cmailOldMove)) {
12863                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12864                 } else {
12865                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12866                 }
12867                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12868             }
12869             break;
12870           default:
12871             break;
12872         }
12873     }
12874 }
12875
12876
12877 void
12878 StopObservingEvent()
12879 {
12880     /* Stop observing current games */
12881     SendToICS(ics_prefix);
12882     SendToICS("unobserve\n");
12883 }
12884
12885 void
12886 StopExaminingEvent()
12887 {
12888     /* Stop observing current game */
12889     SendToICS(ics_prefix);
12890     SendToICS("unexamine\n");
12891 }
12892
12893 void
12894 ForwardInner(target)
12895      int target;
12896 {
12897     int limit;
12898
12899     if (appData.debugMode)
12900         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12901                 target, currentMove, forwardMostMove);
12902
12903     if (gameMode == EditPosition)
12904       return;
12905
12906     if (gameMode == PlayFromGameFile && !pausing)
12907       PauseEvent();
12908
12909     if (gameMode == IcsExamining && pausing)
12910       limit = pauseExamForwardMostMove;
12911     else
12912       limit = forwardMostMove;
12913
12914     if (target > limit) target = limit;
12915
12916     if (target > 0 && moveList[target - 1][0]) {
12917         int fromX, fromY, toX, toY;
12918         toX = moveList[target - 1][2] - AAA;
12919         toY = moveList[target - 1][3] - ONE;
12920         if (moveList[target - 1][1] == '@') {
12921             if (appData.highlightLastMove) {
12922                 SetHighlights(-1, -1, toX, toY);
12923             }
12924         } else {
12925             fromX = moveList[target - 1][0] - AAA;
12926             fromY = moveList[target - 1][1] - ONE;
12927             if (target == currentMove + 1) {
12928                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12929             }
12930             if (appData.highlightLastMove) {
12931                 SetHighlights(fromX, fromY, toX, toY);
12932             }
12933         }
12934     }
12935     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12936         gameMode == Training || gameMode == PlayFromGameFile ||
12937         gameMode == AnalyzeFile) {
12938         while (currentMove < target) {
12939             SendMoveToProgram(currentMove++, &first);
12940         }
12941     } else {
12942         currentMove = target;
12943     }
12944
12945     if (gameMode == EditGame || gameMode == EndOfGame) {
12946         whiteTimeRemaining = timeRemaining[0][currentMove];
12947         blackTimeRemaining = timeRemaining[1][currentMove];
12948     }
12949     DisplayBothClocks();
12950     DisplayMove(currentMove - 1);
12951     DrawPosition(FALSE, boards[currentMove]);
12952     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12953     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12954         DisplayComment(currentMove - 1, commentList[currentMove]);
12955     }
12956 }
12957
12958
12959 void
12960 ForwardEvent()
12961 {
12962     if (gameMode == IcsExamining && !pausing) {
12963         SendToICS(ics_prefix);
12964         SendToICS("forward\n");
12965     } else {
12966         ForwardInner(currentMove + 1);
12967     }
12968 }
12969
12970 void
12971 ToEndEvent()
12972 {
12973     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12974         /* to optimze, we temporarily turn off analysis mode while we feed
12975          * the remaining moves to the engine. Otherwise we get analysis output
12976          * after each move.
12977          */
12978         if (first.analysisSupport) {
12979           SendToProgram("exit\nforce\n", &first);
12980           first.analyzing = FALSE;
12981         }
12982     }
12983
12984     if (gameMode == IcsExamining && !pausing) {
12985         SendToICS(ics_prefix);
12986         SendToICS("forward 999999\n");
12987     } else {
12988         ForwardInner(forwardMostMove);
12989     }
12990
12991     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12992         /* we have fed all the moves, so reactivate analysis mode */
12993         SendToProgram("analyze\n", &first);
12994         first.analyzing = TRUE;
12995         /*first.maybeThinking = TRUE;*/
12996         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12997     }
12998 }
12999
13000 void
13001 BackwardInner(target)
13002      int target;
13003 {
13004     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13005
13006     if (appData.debugMode)
13007         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13008                 target, currentMove, forwardMostMove);
13009
13010     if (gameMode == EditPosition) return;
13011     if (currentMove <= backwardMostMove) {
13012         ClearHighlights();
13013         DrawPosition(full_redraw, boards[currentMove]);
13014         return;
13015     }
13016     if (gameMode == PlayFromGameFile && !pausing)
13017       PauseEvent();
13018
13019     if (moveList[target][0]) {
13020         int fromX, fromY, toX, toY;
13021         toX = moveList[target][2] - AAA;
13022         toY = moveList[target][3] - ONE;
13023         if (moveList[target][1] == '@') {
13024             if (appData.highlightLastMove) {
13025                 SetHighlights(-1, -1, toX, toY);
13026             }
13027         } else {
13028             fromX = moveList[target][0] - AAA;
13029             fromY = moveList[target][1] - ONE;
13030             if (target == currentMove - 1) {
13031                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13032             }
13033             if (appData.highlightLastMove) {
13034                 SetHighlights(fromX, fromY, toX, toY);
13035             }
13036         }
13037     }
13038     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13039         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13040         while (currentMove > target) {
13041             SendToProgram("undo\n", &first);
13042             currentMove--;
13043         }
13044     } else {
13045         currentMove = target;
13046     }
13047
13048     if (gameMode == EditGame || gameMode == EndOfGame) {
13049         whiteTimeRemaining = timeRemaining[0][currentMove];
13050         blackTimeRemaining = timeRemaining[1][currentMove];
13051     }
13052     DisplayBothClocks();
13053     DisplayMove(currentMove - 1);
13054     DrawPosition(full_redraw, boards[currentMove]);
13055     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13056     // [HGM] PV info: routine tests if comment empty
13057     DisplayComment(currentMove - 1, commentList[currentMove]);
13058 }
13059
13060 void
13061 BackwardEvent()
13062 {
13063     if (gameMode == IcsExamining && !pausing) {
13064         SendToICS(ics_prefix);
13065         SendToICS("backward\n");
13066     } else {
13067         BackwardInner(currentMove - 1);
13068     }
13069 }
13070
13071 void
13072 ToStartEvent()
13073 {
13074     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13075         /* to optimize, we temporarily turn off analysis mode while we undo
13076          * all the moves. Otherwise we get analysis output after each undo.
13077          */
13078         if (first.analysisSupport) {
13079           SendToProgram("exit\nforce\n", &first);
13080           first.analyzing = FALSE;
13081         }
13082     }
13083
13084     if (gameMode == IcsExamining && !pausing) {
13085         SendToICS(ics_prefix);
13086         SendToICS("backward 999999\n");
13087     } else {
13088         BackwardInner(backwardMostMove);
13089     }
13090
13091     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13092         /* we have fed all the moves, so reactivate analysis mode */
13093         SendToProgram("analyze\n", &first);
13094         first.analyzing = TRUE;
13095         /*first.maybeThinking = TRUE;*/
13096         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13097     }
13098 }
13099
13100 void
13101 ToNrEvent(int to)
13102 {
13103   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13104   if (to >= forwardMostMove) to = forwardMostMove;
13105   if (to <= backwardMostMove) to = backwardMostMove;
13106   if (to < currentMove) {
13107     BackwardInner(to);
13108   } else {
13109     ForwardInner(to);
13110   }
13111 }
13112
13113 void
13114 RevertEvent(Boolean annotate)
13115 {
13116     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13117         return;
13118     }
13119     if (gameMode != IcsExamining) {
13120         DisplayError(_("You are not examining a game"), 0);
13121         return;
13122     }
13123     if (pausing) {
13124         DisplayError(_("You can't revert while pausing"), 0);
13125         return;
13126     }
13127     SendToICS(ics_prefix);
13128     SendToICS("revert\n");
13129 }
13130
13131 void
13132 RetractMoveEvent()
13133 {
13134     switch (gameMode) {
13135       case MachinePlaysWhite:
13136       case MachinePlaysBlack:
13137         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13138             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13139             return;
13140         }
13141         if (forwardMostMove < 2) return;
13142         currentMove = forwardMostMove = forwardMostMove - 2;
13143         whiteTimeRemaining = timeRemaining[0][currentMove];
13144         blackTimeRemaining = timeRemaining[1][currentMove];
13145         DisplayBothClocks();
13146         DisplayMove(currentMove - 1);
13147         ClearHighlights();/*!! could figure this out*/
13148         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13149         SendToProgram("remove\n", &first);
13150         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13151         break;
13152
13153       case BeginningOfGame:
13154       default:
13155         break;
13156
13157       case IcsPlayingWhite:
13158       case IcsPlayingBlack:
13159         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13160             SendToICS(ics_prefix);
13161             SendToICS("takeback 2\n");
13162         } else {
13163             SendToICS(ics_prefix);
13164             SendToICS("takeback 1\n");
13165         }
13166         break;
13167     }
13168 }
13169
13170 void
13171 MoveNowEvent()
13172 {
13173     ChessProgramState *cps;
13174
13175     switch (gameMode) {
13176       case MachinePlaysWhite:
13177         if (!WhiteOnMove(forwardMostMove)) {
13178             DisplayError(_("It is your turn"), 0);
13179             return;
13180         }
13181         cps = &first;
13182         break;
13183       case MachinePlaysBlack:
13184         if (WhiteOnMove(forwardMostMove)) {
13185             DisplayError(_("It is your turn"), 0);
13186             return;
13187         }
13188         cps = &first;
13189         break;
13190       case TwoMachinesPlay:
13191         if (WhiteOnMove(forwardMostMove) ==
13192             (first.twoMachinesColor[0] == 'w')) {
13193             cps = &first;
13194         } else {
13195             cps = &second;
13196         }
13197         break;
13198       case BeginningOfGame:
13199       default:
13200         return;
13201     }
13202     SendToProgram("?\n", cps);
13203 }
13204
13205 void
13206 TruncateGameEvent()
13207 {
13208     EditGameEvent();
13209     if (gameMode != EditGame) return;
13210     TruncateGame();
13211 }
13212
13213 void
13214 TruncateGame()
13215 {
13216     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13217     if (forwardMostMove > currentMove) {
13218         if (gameInfo.resultDetails != NULL) {
13219             free(gameInfo.resultDetails);
13220             gameInfo.resultDetails = NULL;
13221             gameInfo.result = GameUnfinished;
13222         }
13223         forwardMostMove = currentMove;
13224         HistorySet(parseList, backwardMostMove, forwardMostMove,
13225                    currentMove-1);
13226     }
13227 }
13228
13229 void
13230 HintEvent()
13231 {
13232     if (appData.noChessProgram) return;
13233     switch (gameMode) {
13234       case MachinePlaysWhite:
13235         if (WhiteOnMove(forwardMostMove)) {
13236             DisplayError(_("Wait until your turn"), 0);
13237             return;
13238         }
13239         break;
13240       case BeginningOfGame:
13241       case MachinePlaysBlack:
13242         if (!WhiteOnMove(forwardMostMove)) {
13243             DisplayError(_("Wait until your turn"), 0);
13244             return;
13245         }
13246         break;
13247       default:
13248         DisplayError(_("No hint available"), 0);
13249         return;
13250     }
13251     SendToProgram("hint\n", &first);
13252     hintRequested = TRUE;
13253 }
13254
13255 void
13256 BookEvent()
13257 {
13258     if (appData.noChessProgram) return;
13259     switch (gameMode) {
13260       case MachinePlaysWhite:
13261         if (WhiteOnMove(forwardMostMove)) {
13262             DisplayError(_("Wait until your turn"), 0);
13263             return;
13264         }
13265         break;
13266       case BeginningOfGame:
13267       case MachinePlaysBlack:
13268         if (!WhiteOnMove(forwardMostMove)) {
13269             DisplayError(_("Wait until your turn"), 0);
13270             return;
13271         }
13272         break;
13273       case EditPosition:
13274         EditPositionDone(TRUE);
13275         break;
13276       case TwoMachinesPlay:
13277         return;
13278       default:
13279         break;
13280     }
13281     SendToProgram("bk\n", &first);
13282     bookOutput[0] = NULLCHAR;
13283     bookRequested = TRUE;
13284 }
13285
13286 void
13287 AboutGameEvent()
13288 {
13289     char *tags = PGNTags(&gameInfo);
13290     TagsPopUp(tags, CmailMsg());
13291     free(tags);
13292 }
13293
13294 /* end button procedures */
13295
13296 void
13297 PrintPosition(fp, move)
13298      FILE *fp;
13299      int move;
13300 {
13301     int i, j;
13302
13303     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13304         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13305             char c = PieceToChar(boards[move][i][j]);
13306             fputc(c == 'x' ? '.' : c, fp);
13307             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13308         }
13309     }
13310     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13311       fprintf(fp, "white to play\n");
13312     else
13313       fprintf(fp, "black to play\n");
13314 }
13315
13316 void
13317 PrintOpponents(fp)
13318      FILE *fp;
13319 {
13320     if (gameInfo.white != NULL) {
13321         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13322     } else {
13323         fprintf(fp, "\n");
13324     }
13325 }
13326
13327 /* Find last component of program's own name, using some heuristics */
13328 void
13329 TidyProgramName(prog, host, buf)
13330      char *prog, *host, buf[MSG_SIZ];
13331 {
13332     char *p, *q;
13333     int local = (strcmp(host, "localhost") == 0);
13334     while (!local && (p = strchr(prog, ';')) != NULL) {
13335         p++;
13336         while (*p == ' ') p++;
13337         prog = p;
13338     }
13339     if (*prog == '"' || *prog == '\'') {
13340         q = strchr(prog + 1, *prog);
13341     } else {
13342         q = strchr(prog, ' ');
13343     }
13344     if (q == NULL) q = prog + strlen(prog);
13345     p = q;
13346     while (p >= prog && *p != '/' && *p != '\\') p--;
13347     p++;
13348     if(p == prog && *p == '"') p++;
13349     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13350     memcpy(buf, p, q - p);
13351     buf[q - p] = NULLCHAR;
13352     if (!local) {
13353         strcat(buf, "@");
13354         strcat(buf, host);
13355     }
13356 }
13357
13358 char *
13359 TimeControlTagValue()
13360 {
13361     char buf[MSG_SIZ];
13362     if (!appData.clockMode) {
13363       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13364     } else if (movesPerSession > 0) {
13365       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13366     } else if (timeIncrement == 0) {
13367       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13368     } else {
13369       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13370     }
13371     return StrSave(buf);
13372 }
13373
13374 void
13375 SetGameInfo()
13376 {
13377     /* This routine is used only for certain modes */
13378     VariantClass v = gameInfo.variant;
13379     ChessMove r = GameUnfinished;
13380     char *p = NULL;
13381
13382     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13383         r = gameInfo.result;
13384         p = gameInfo.resultDetails;
13385         gameInfo.resultDetails = NULL;
13386     }
13387     ClearGameInfo(&gameInfo);
13388     gameInfo.variant = v;
13389
13390     switch (gameMode) {
13391       case MachinePlaysWhite:
13392         gameInfo.event = StrSave( appData.pgnEventHeader );
13393         gameInfo.site = StrSave(HostName());
13394         gameInfo.date = PGNDate();
13395         gameInfo.round = StrSave("-");
13396         gameInfo.white = StrSave(first.tidy);
13397         gameInfo.black = StrSave(UserName());
13398         gameInfo.timeControl = TimeControlTagValue();
13399         break;
13400
13401       case MachinePlaysBlack:
13402         gameInfo.event = StrSave( appData.pgnEventHeader );
13403         gameInfo.site = StrSave(HostName());
13404         gameInfo.date = PGNDate();
13405         gameInfo.round = StrSave("-");
13406         gameInfo.white = StrSave(UserName());
13407         gameInfo.black = StrSave(first.tidy);
13408         gameInfo.timeControl = TimeControlTagValue();
13409         break;
13410
13411       case TwoMachinesPlay:
13412         gameInfo.event = StrSave( appData.pgnEventHeader );
13413         gameInfo.site = StrSave(HostName());
13414         gameInfo.date = PGNDate();
13415         if (matchGame > 0) {
13416             char buf[MSG_SIZ];
13417             snprintf(buf, MSG_SIZ, "%d", matchGame);
13418             gameInfo.round = StrSave(buf);
13419         } else {
13420             gameInfo.round = StrSave("-");
13421         }
13422         if (first.twoMachinesColor[0] == 'w') {
13423             gameInfo.white = StrSave(first.tidy);
13424             gameInfo.black = StrSave(second.tidy);
13425         } else {
13426             gameInfo.white = StrSave(second.tidy);
13427             gameInfo.black = StrSave(first.tidy);
13428         }
13429         gameInfo.timeControl = TimeControlTagValue();
13430         break;
13431
13432       case EditGame:
13433         gameInfo.event = StrSave("Edited game");
13434         gameInfo.site = StrSave(HostName());
13435         gameInfo.date = PGNDate();
13436         gameInfo.round = StrSave("-");
13437         gameInfo.white = StrSave("-");
13438         gameInfo.black = StrSave("-");
13439         gameInfo.result = r;
13440         gameInfo.resultDetails = p;
13441         break;
13442
13443       case EditPosition:
13444         gameInfo.event = StrSave("Edited position");
13445         gameInfo.site = StrSave(HostName());
13446         gameInfo.date = PGNDate();
13447         gameInfo.round = StrSave("-");
13448         gameInfo.white = StrSave("-");
13449         gameInfo.black = StrSave("-");
13450         break;
13451
13452       case IcsPlayingWhite:
13453       case IcsPlayingBlack:
13454       case IcsObserving:
13455       case IcsExamining:
13456         break;
13457
13458       case PlayFromGameFile:
13459         gameInfo.event = StrSave("Game from non-PGN file");
13460         gameInfo.site = StrSave(HostName());
13461         gameInfo.date = PGNDate();
13462         gameInfo.round = StrSave("-");
13463         gameInfo.white = StrSave("?");
13464         gameInfo.black = StrSave("?");
13465         break;
13466
13467       default:
13468         break;
13469     }
13470 }
13471
13472 void
13473 ReplaceComment(index, text)
13474      int index;
13475      char *text;
13476 {
13477     int len;
13478     char *p;
13479     float score;
13480
13481     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13482        pvInfoList[index-1].depth == len &&
13483        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13484        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13485     while (*text == '\n') text++;
13486     len = strlen(text);
13487     while (len > 0 && text[len - 1] == '\n') len--;
13488
13489     if (commentList[index] != NULL)
13490       free(commentList[index]);
13491
13492     if (len == 0) {
13493         commentList[index] = NULL;
13494         return;
13495     }
13496   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13497       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13498       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13499     commentList[index] = (char *) malloc(len + 2);
13500     strncpy(commentList[index], text, len);
13501     commentList[index][len] = '\n';
13502     commentList[index][len + 1] = NULLCHAR;
13503   } else {
13504     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13505     char *p;
13506     commentList[index] = (char *) malloc(len + 7);
13507     safeStrCpy(commentList[index], "{\n", 3);
13508     safeStrCpy(commentList[index]+2, text, len+1);
13509     commentList[index][len+2] = NULLCHAR;
13510     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13511     strcat(commentList[index], "\n}\n");
13512   }
13513 }
13514
13515 void
13516 CrushCRs(text)
13517      char *text;
13518 {
13519   char *p = text;
13520   char *q = text;
13521   char ch;
13522
13523   do {
13524     ch = *p++;
13525     if (ch == '\r') continue;
13526     *q++ = ch;
13527   } while (ch != '\0');
13528 }
13529
13530 void
13531 AppendComment(index, text, addBraces)
13532      int index;
13533      char *text;
13534      Boolean addBraces; // [HGM] braces: tells if we should add {}
13535 {
13536     int oldlen, len;
13537     char *old;
13538
13539 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13540     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13541
13542     CrushCRs(text);
13543     while (*text == '\n') text++;
13544     len = strlen(text);
13545     while (len > 0 && text[len - 1] == '\n') len--;
13546
13547     if (len == 0) return;
13548
13549     if (commentList[index] != NULL) {
13550         old = commentList[index];
13551         oldlen = strlen(old);
13552         while(commentList[index][oldlen-1] ==  '\n')
13553           commentList[index][--oldlen] = NULLCHAR;
13554         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13555         safeStrCpy(commentList[index], old, oldlen + len + 6);
13556         free(old);
13557         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13558         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13559           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13560           while (*text == '\n') { text++; len--; }
13561           commentList[index][--oldlen] = NULLCHAR;
13562       }
13563         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13564         else          strcat(commentList[index], "\n");
13565         strcat(commentList[index], text);
13566         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13567         else          strcat(commentList[index], "\n");
13568     } else {
13569         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13570         if(addBraces)
13571           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13572         else commentList[index][0] = NULLCHAR;
13573         strcat(commentList[index], text);
13574         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13575         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13576     }
13577 }
13578
13579 static char * FindStr( char * text, char * sub_text )
13580 {
13581     char * result = strstr( text, sub_text );
13582
13583     if( result != NULL ) {
13584         result += strlen( sub_text );
13585     }
13586
13587     return result;
13588 }
13589
13590 /* [AS] Try to extract PV info from PGN comment */
13591 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13592 char *GetInfoFromComment( int index, char * text )
13593 {
13594     char * sep = text, *p;
13595
13596     if( text != NULL && index > 0 ) {
13597         int score = 0;
13598         int depth = 0;
13599         int time = -1, sec = 0, deci;
13600         char * s_eval = FindStr( text, "[%eval " );
13601         char * s_emt = FindStr( text, "[%emt " );
13602
13603         if( s_eval != NULL || s_emt != NULL ) {
13604             /* New style */
13605             char delim;
13606
13607             if( s_eval != NULL ) {
13608                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13609                     return text;
13610                 }
13611
13612                 if( delim != ']' ) {
13613                     return text;
13614                 }
13615             }
13616
13617             if( s_emt != NULL ) {
13618             }
13619                 return text;
13620         }
13621         else {
13622             /* We expect something like: [+|-]nnn.nn/dd */
13623             int score_lo = 0;
13624
13625             if(*text != '{') return text; // [HGM] braces: must be normal comment
13626
13627             sep = strchr( text, '/' );
13628             if( sep == NULL || sep < (text+4) ) {
13629                 return text;
13630             }
13631
13632             p = text;
13633             if(p[1] == '(') { // comment starts with PV
13634                p = strchr(p, ')'); // locate end of PV
13635                if(p == NULL || sep < p+5) return text;
13636                // at this point we have something like "{(.*) +0.23/6 ..."
13637                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13638                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13639                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13640             }
13641             time = -1; sec = -1; deci = -1;
13642             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13643                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13644                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13645                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13646                 return text;
13647             }
13648
13649             if( score_lo < 0 || score_lo >= 100 ) {
13650                 return text;
13651             }
13652
13653             if(sec >= 0) time = 600*time + 10*sec; else
13654             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13655
13656             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13657
13658             /* [HGM] PV time: now locate end of PV info */
13659             while( *++sep >= '0' && *sep <= '9'); // strip depth
13660             if(time >= 0)
13661             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13662             if(sec >= 0)
13663             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13664             if(deci >= 0)
13665             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13666             while(*sep == ' ') sep++;
13667         }
13668
13669         if( depth <= 0 ) {
13670             return text;
13671         }
13672
13673         if( time < 0 ) {
13674             time = -1;
13675         }
13676
13677         pvInfoList[index-1].depth = depth;
13678         pvInfoList[index-1].score = score;
13679         pvInfoList[index-1].time  = 10*time; // centi-sec
13680         if(*sep == '}') *sep = 0; else *--sep = '{';
13681         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13682     }
13683     return sep;
13684 }
13685
13686 void
13687 SendToProgram(message, cps)
13688      char *message;
13689      ChessProgramState *cps;
13690 {
13691     int count, outCount, error;
13692     char buf[MSG_SIZ];
13693
13694     if (cps->pr == NULL) return;
13695     Attention(cps);
13696
13697     if (appData.debugMode) {
13698         TimeMark now;
13699         GetTimeMark(&now);
13700         fprintf(debugFP, "%ld >%-6s: %s",
13701                 SubtractTimeMarks(&now, &programStartTime),
13702                 cps->which, message);
13703     }
13704
13705     count = strlen(message);
13706     outCount = OutputToProcess(cps->pr, message, count, &error);
13707     if (outCount < count && !exiting
13708                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13709       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13710         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13711             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13712                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13713                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13714             } else {
13715                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13716             }
13717             gameInfo.resultDetails = StrSave(buf);
13718         }
13719         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13720     }
13721 }
13722
13723 void
13724 ReceiveFromProgram(isr, closure, message, count, error)
13725      InputSourceRef isr;
13726      VOIDSTAR closure;
13727      char *message;
13728      int count;
13729      int error;
13730 {
13731     char *end_str;
13732     char buf[MSG_SIZ];
13733     ChessProgramState *cps = (ChessProgramState *)closure;
13734
13735     if (isr != cps->isr) return; /* Killed intentionally */
13736     if (count <= 0) {
13737         if (count == 0) {
13738             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13739                     _(cps->which), cps->program);
13740         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13741                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13742                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13743                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13744                 } else {
13745                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13746                 }
13747                 gameInfo.resultDetails = StrSave(buf);
13748             }
13749             RemoveInputSource(cps->isr);
13750             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13751         } else {
13752             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13753                     _(cps->which), cps->program);
13754             RemoveInputSource(cps->isr);
13755
13756             /* [AS] Program is misbehaving badly... kill it */
13757             if( count == -2 ) {
13758                 DestroyChildProcess( cps->pr, 9 );
13759                 cps->pr = NoProc;
13760             }
13761
13762             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13763         }
13764         return;
13765     }
13766
13767     if ((end_str = strchr(message, '\r')) != NULL)
13768       *end_str = NULLCHAR;
13769     if ((end_str = strchr(message, '\n')) != NULL)
13770       *end_str = NULLCHAR;
13771
13772     if (appData.debugMode) {
13773         TimeMark now; int print = 1;
13774         char *quote = ""; char c; int i;
13775
13776         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13777                 char start = message[0];
13778                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13779                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13780                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13781                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13782                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13783                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13784                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13785                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13786                    sscanf(message, "hint: %c", &c)!=1 && 
13787                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13788                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13789                     print = (appData.engineComments >= 2);
13790                 }
13791                 message[0] = start; // restore original message
13792         }
13793         if(print) {
13794                 GetTimeMark(&now);
13795                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13796                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13797                         quote,
13798                         message);
13799         }
13800     }
13801
13802     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13803     if (appData.icsEngineAnalyze) {
13804         if (strstr(message, "whisper") != NULL ||
13805              strstr(message, "kibitz") != NULL ||
13806             strstr(message, "tellics") != NULL) return;
13807     }
13808
13809     HandleMachineMove(message, cps);
13810 }
13811
13812
13813 void
13814 SendTimeControl(cps, mps, tc, inc, sd, st)
13815      ChessProgramState *cps;
13816      int mps, inc, sd, st;
13817      long tc;
13818 {
13819     char buf[MSG_SIZ];
13820     int seconds;
13821
13822     if( timeControl_2 > 0 ) {
13823         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13824             tc = timeControl_2;
13825         }
13826     }
13827     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13828     inc /= cps->timeOdds;
13829     st  /= cps->timeOdds;
13830
13831     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13832
13833     if (st > 0) {
13834       /* Set exact time per move, normally using st command */
13835       if (cps->stKludge) {
13836         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13837         seconds = st % 60;
13838         if (seconds == 0) {
13839           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13840         } else {
13841           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13842         }
13843       } else {
13844         snprintf(buf, MSG_SIZ, "st %d\n", st);
13845       }
13846     } else {
13847       /* Set conventional or incremental time control, using level command */
13848       if (seconds == 0) {
13849         /* Note old gnuchess bug -- minutes:seconds used to not work.
13850            Fixed in later versions, but still avoid :seconds
13851            when seconds is 0. */
13852         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13853       } else {
13854         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13855                  seconds, inc/1000.);
13856       }
13857     }
13858     SendToProgram(buf, cps);
13859
13860     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13861     /* Orthogonally, limit search to given depth */
13862     if (sd > 0) {
13863       if (cps->sdKludge) {
13864         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13865       } else {
13866         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13867       }
13868       SendToProgram(buf, cps);
13869     }
13870
13871     if(cps->nps >= 0) { /* [HGM] nps */
13872         if(cps->supportsNPS == FALSE)
13873           cps->nps = -1; // don't use if engine explicitly says not supported!
13874         else {
13875           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13876           SendToProgram(buf, cps);
13877         }
13878     }
13879 }
13880
13881 ChessProgramState *WhitePlayer()
13882 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13883 {
13884     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13885        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13886         return &second;
13887     return &first;
13888 }
13889
13890 void
13891 SendTimeRemaining(cps, machineWhite)
13892      ChessProgramState *cps;
13893      int /*boolean*/ machineWhite;
13894 {
13895     char message[MSG_SIZ];
13896     long time, otime;
13897
13898     /* Note: this routine must be called when the clocks are stopped
13899        or when they have *just* been set or switched; otherwise
13900        it will be off by the time since the current tick started.
13901     */
13902     if (machineWhite) {
13903         time = whiteTimeRemaining / 10;
13904         otime = blackTimeRemaining / 10;
13905     } else {
13906         time = blackTimeRemaining / 10;
13907         otime = whiteTimeRemaining / 10;
13908     }
13909     /* [HGM] translate opponent's time by time-odds factor */
13910     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13911     if (appData.debugMode) {
13912         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13913     }
13914
13915     if (time <= 0) time = 1;
13916     if (otime <= 0) otime = 1;
13917
13918     snprintf(message, MSG_SIZ, "time %ld\n", time);
13919     SendToProgram(message, cps);
13920
13921     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13922     SendToProgram(message, cps);
13923 }
13924
13925 int
13926 BoolFeature(p, name, loc, cps)
13927      char **p;
13928      char *name;
13929      int *loc;
13930      ChessProgramState *cps;
13931 {
13932   char buf[MSG_SIZ];
13933   int len = strlen(name);
13934   int val;
13935
13936   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13937     (*p) += len + 1;
13938     sscanf(*p, "%d", &val);
13939     *loc = (val != 0);
13940     while (**p && **p != ' ')
13941       (*p)++;
13942     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13943     SendToProgram(buf, cps);
13944     return TRUE;
13945   }
13946   return FALSE;
13947 }
13948
13949 int
13950 IntFeature(p, name, loc, cps)
13951      char **p;
13952      char *name;
13953      int *loc;
13954      ChessProgramState *cps;
13955 {
13956   char buf[MSG_SIZ];
13957   int len = strlen(name);
13958   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13959     (*p) += len + 1;
13960     sscanf(*p, "%d", loc);
13961     while (**p && **p != ' ') (*p)++;
13962     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13963     SendToProgram(buf, cps);
13964     return TRUE;
13965   }
13966   return FALSE;
13967 }
13968
13969 int
13970 StringFeature(p, name, loc, cps)
13971      char **p;
13972      char *name;
13973      char loc[];
13974      ChessProgramState *cps;
13975 {
13976   char buf[MSG_SIZ];
13977   int len = strlen(name);
13978   if (strncmp((*p), name, len) == 0
13979       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13980     (*p) += len + 2;
13981     sscanf(*p, "%[^\"]", loc);
13982     while (**p && **p != '\"') (*p)++;
13983     if (**p == '\"') (*p)++;
13984     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13985     SendToProgram(buf, cps);
13986     return TRUE;
13987   }
13988   return FALSE;
13989 }
13990
13991 int
13992 ParseOption(Option *opt, ChessProgramState *cps)
13993 // [HGM] options: process the string that defines an engine option, and determine
13994 // name, type, default value, and allowed value range
13995 {
13996         char *p, *q, buf[MSG_SIZ];
13997         int n, min = (-1)<<31, max = 1<<31, def;
13998
13999         if(p = strstr(opt->name, " -spin ")) {
14000             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14001             if(max < min) max = min; // enforce consistency
14002             if(def < min) def = min;
14003             if(def > max) def = max;
14004             opt->value = def;
14005             opt->min = min;
14006             opt->max = max;
14007             opt->type = Spin;
14008         } else if((p = strstr(opt->name, " -slider "))) {
14009             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14010             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14011             if(max < min) max = min; // enforce consistency
14012             if(def < min) def = min;
14013             if(def > max) def = max;
14014             opt->value = def;
14015             opt->min = min;
14016             opt->max = max;
14017             opt->type = Spin; // Slider;
14018         } else if((p = strstr(opt->name, " -string "))) {
14019             opt->textValue = p+9;
14020             opt->type = TextBox;
14021         } else if((p = strstr(opt->name, " -file "))) {
14022             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14023             opt->textValue = p+7;
14024             opt->type = FileName; // FileName;
14025         } else if((p = strstr(opt->name, " -path "))) {
14026             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14027             opt->textValue = p+7;
14028             opt->type = PathName; // PathName;
14029         } else if(p = strstr(opt->name, " -check ")) {
14030             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14031             opt->value = (def != 0);
14032             opt->type = CheckBox;
14033         } else if(p = strstr(opt->name, " -combo ")) {
14034             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14035             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14036             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14037             opt->value = n = 0;
14038             while(q = StrStr(q, " /// ")) {
14039                 n++; *q = 0;    // count choices, and null-terminate each of them
14040                 q += 5;
14041                 if(*q == '*') { // remember default, which is marked with * prefix
14042                     q++;
14043                     opt->value = n;
14044                 }
14045                 cps->comboList[cps->comboCnt++] = q;
14046             }
14047             cps->comboList[cps->comboCnt++] = NULL;
14048             opt->max = n + 1;
14049             opt->type = ComboBox;
14050         } else if(p = strstr(opt->name, " -button")) {
14051             opt->type = Button;
14052         } else if(p = strstr(opt->name, " -save")) {
14053             opt->type = SaveButton;
14054         } else return FALSE;
14055         *p = 0; // terminate option name
14056         // now look if the command-line options define a setting for this engine option.
14057         if(cps->optionSettings && cps->optionSettings[0])
14058             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14059         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14060           snprintf(buf, MSG_SIZ, "option %s", p);
14061                 if(p = strstr(buf, ",")) *p = 0;
14062                 if(q = strchr(buf, '=')) switch(opt->type) {
14063                     case ComboBox:
14064                         for(n=0; n<opt->max; n++)
14065                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14066                         break;
14067                     case TextBox:
14068                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14069                         break;
14070                     case Spin:
14071                     case CheckBox:
14072                         opt->value = atoi(q+1);
14073                     default:
14074                         break;
14075                 }
14076                 strcat(buf, "\n");
14077                 SendToProgram(buf, cps);
14078         }
14079         return TRUE;
14080 }
14081
14082 void
14083 FeatureDone(cps, val)
14084      ChessProgramState* cps;
14085      int val;
14086 {
14087   DelayedEventCallback cb = GetDelayedEvent();
14088   if ((cb == InitBackEnd3 && cps == &first) ||
14089       (cb == SettingsMenuIfReady && cps == &second) ||
14090       (cb == TwoMachinesEventIfReady && cps == &second)) {
14091     CancelDelayedEvent();
14092     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14093   }
14094   cps->initDone = val;
14095 }
14096
14097 /* Parse feature command from engine */
14098 void
14099 ParseFeatures(args, cps)
14100      char* args;
14101      ChessProgramState *cps;
14102 {
14103   char *p = args;
14104   char *q;
14105   int val;
14106   char buf[MSG_SIZ];
14107
14108   for (;;) {
14109     while (*p == ' ') p++;
14110     if (*p == NULLCHAR) return;
14111
14112     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14113     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14114     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14115     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14116     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14117     if (BoolFeature(&p, "reuse", &val, cps)) {
14118       /* Engine can disable reuse, but can't enable it if user said no */
14119       if (!val) cps->reuse = FALSE;
14120       continue;
14121     }
14122     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14123     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14124       if (gameMode == TwoMachinesPlay) {
14125         DisplayTwoMachinesTitle();
14126       } else {
14127         DisplayTitle("");
14128       }
14129       continue;
14130     }
14131     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14132     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14133     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14134     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14135     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14136     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14137     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14138     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14139     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14140     if (IntFeature(&p, "done", &val, cps)) {
14141       FeatureDone(cps, val);
14142       continue;
14143     }
14144     /* Added by Tord: */
14145     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14146     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14147     /* End of additions by Tord */
14148
14149     /* [HGM] added features: */
14150     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14151     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14152     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14153     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14154     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14155     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14156     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14157         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14158           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14159             SendToProgram(buf, cps);
14160             continue;
14161         }
14162         if(cps->nrOptions >= MAX_OPTIONS) {
14163             cps->nrOptions--;
14164             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14165             DisplayError(buf, 0);
14166         }
14167         continue;
14168     }
14169     /* End of additions by HGM */
14170
14171     /* unknown feature: complain and skip */
14172     q = p;
14173     while (*q && *q != '=') q++;
14174     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14175     SendToProgram(buf, cps);
14176     p = q;
14177     if (*p == '=') {
14178       p++;
14179       if (*p == '\"') {
14180         p++;
14181         while (*p && *p != '\"') p++;
14182         if (*p == '\"') p++;
14183       } else {
14184         while (*p && *p != ' ') p++;
14185       }
14186     }
14187   }
14188
14189 }
14190
14191 void
14192 PeriodicUpdatesEvent(newState)
14193      int newState;
14194 {
14195     if (newState == appData.periodicUpdates)
14196       return;
14197
14198     appData.periodicUpdates=newState;
14199
14200     /* Display type changes, so update it now */
14201 //    DisplayAnalysis();
14202
14203     /* Get the ball rolling again... */
14204     if (newState) {
14205         AnalysisPeriodicEvent(1);
14206         StartAnalysisClock();
14207     }
14208 }
14209
14210 void
14211 PonderNextMoveEvent(newState)
14212      int newState;
14213 {
14214     if (newState == appData.ponderNextMove) return;
14215     if (gameMode == EditPosition) EditPositionDone(TRUE);
14216     if (newState) {
14217         SendToProgram("hard\n", &first);
14218         if (gameMode == TwoMachinesPlay) {
14219             SendToProgram("hard\n", &second);
14220         }
14221     } else {
14222         SendToProgram("easy\n", &first);
14223         thinkOutput[0] = NULLCHAR;
14224         if (gameMode == TwoMachinesPlay) {
14225             SendToProgram("easy\n", &second);
14226         }
14227     }
14228     appData.ponderNextMove = newState;
14229 }
14230
14231 void
14232 NewSettingEvent(option, feature, command, value)
14233      char *command;
14234      int option, value, *feature;
14235 {
14236     char buf[MSG_SIZ];
14237
14238     if (gameMode == EditPosition) EditPositionDone(TRUE);
14239     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14240     if(feature == NULL || *feature) SendToProgram(buf, &first);
14241     if (gameMode == TwoMachinesPlay) {
14242         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14243     }
14244 }
14245
14246 void
14247 ShowThinkingEvent()
14248 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14249 {
14250     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14251     int newState = appData.showThinking
14252         // [HGM] thinking: other features now need thinking output as well
14253         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14254
14255     if (oldState == newState) return;
14256     oldState = newState;
14257     if (gameMode == EditPosition) EditPositionDone(TRUE);
14258     if (oldState) {
14259         SendToProgram("post\n", &first);
14260         if (gameMode == TwoMachinesPlay) {
14261             SendToProgram("post\n", &second);
14262         }
14263     } else {
14264         SendToProgram("nopost\n", &first);
14265         thinkOutput[0] = NULLCHAR;
14266         if (gameMode == TwoMachinesPlay) {
14267             SendToProgram("nopost\n", &second);
14268         }
14269     }
14270 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14271 }
14272
14273 void
14274 AskQuestionEvent(title, question, replyPrefix, which)
14275      char *title; char *question; char *replyPrefix; char *which;
14276 {
14277   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14278   if (pr == NoProc) return;
14279   AskQuestion(title, question, replyPrefix, pr);
14280 }
14281
14282 void
14283 TypeInEvent(char firstChar)
14284 {
14285     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14286         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14287         gameMode == AnalyzeMode || gameMode == EditGame || \r
14288         gameMode == EditPosition || gameMode == IcsExamining ||\r
14289         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14290         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14291                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14292                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14293         gameMode == Training) PopUpMoveDialog(firstChar);
14294 }
14295
14296 void
14297 TypeInDoneEvent(char *move)
14298 {
14299         Board board;
14300         int n, fromX, fromY, toX, toY;
14301         char promoChar;
14302         ChessMove moveType;\r
14303
14304         // [HGM] FENedit\r
14305         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14306                 EditPositionPasteFEN(move);\r
14307                 return;\r
14308         }\r
14309         // [HGM] movenum: allow move number to be typed in any mode\r
14310         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14311           ToNrEvent(2*n-1);\r
14312           return;\r
14313         }\r
14314
14315       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14316         gameMode != Training) {\r
14317         DisplayMoveError(_("Displayed move is not current"));\r
14318       } else {\r
14319         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14320           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14321         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14322         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14323           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14324           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14325         } else {\r
14326           DisplayMoveError(_("Could not parse move"));\r
14327         }
14328       }\r
14329 }\r
14330
14331 void
14332 DisplayMove(moveNumber)
14333      int moveNumber;
14334 {
14335     char message[MSG_SIZ];
14336     char res[MSG_SIZ];
14337     char cpThinkOutput[MSG_SIZ];
14338
14339     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14340
14341     if (moveNumber == forwardMostMove - 1 ||
14342         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14343
14344         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14345
14346         if (strchr(cpThinkOutput, '\n')) {
14347             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14348         }
14349     } else {
14350         *cpThinkOutput = NULLCHAR;
14351     }
14352
14353     /* [AS] Hide thinking from human user */
14354     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14355         *cpThinkOutput = NULLCHAR;
14356         if( thinkOutput[0] != NULLCHAR ) {
14357             int i;
14358
14359             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14360                 cpThinkOutput[i] = '.';
14361             }
14362             cpThinkOutput[i] = NULLCHAR;
14363             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14364         }
14365     }
14366
14367     if (moveNumber == forwardMostMove - 1 &&
14368         gameInfo.resultDetails != NULL) {
14369         if (gameInfo.resultDetails[0] == NULLCHAR) {
14370           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14371         } else {
14372           snprintf(res, MSG_SIZ, " {%s} %s",
14373                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14374         }
14375     } else {
14376         res[0] = NULLCHAR;
14377     }
14378
14379     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14380         DisplayMessage(res, cpThinkOutput);
14381     } else {
14382       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14383                 WhiteOnMove(moveNumber) ? " " : ".. ",
14384                 parseList[moveNumber], res);
14385         DisplayMessage(message, cpThinkOutput);
14386     }
14387 }
14388
14389 void
14390 DisplayComment(moveNumber, text)
14391      int moveNumber;
14392      char *text;
14393 {
14394     char title[MSG_SIZ];
14395     char buf[8000]; // comment can be long!
14396     int score, depth;
14397
14398     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14399       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14400     } else {
14401       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14402               WhiteOnMove(moveNumber) ? " " : ".. ",
14403               parseList[moveNumber]);
14404     }
14405     // [HGM] PV info: display PV info together with (or as) comment
14406     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14407       if(text == NULL) text = "";
14408       score = pvInfoList[moveNumber].score;
14409       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14410               depth, (pvInfoList[moveNumber].time+50)/100, text);
14411       text = buf;
14412     }
14413     if (text != NULL && (appData.autoDisplayComment || commentUp))
14414         CommentPopUp(title, text);
14415 }
14416
14417 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14418  * might be busy thinking or pondering.  It can be omitted if your
14419  * gnuchess is configured to stop thinking immediately on any user
14420  * input.  However, that gnuchess feature depends on the FIONREAD
14421  * ioctl, which does not work properly on some flavors of Unix.
14422  */
14423 void
14424 Attention(cps)
14425      ChessProgramState *cps;
14426 {
14427 #if ATTENTION
14428     if (!cps->useSigint) return;
14429     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14430     switch (gameMode) {
14431       case MachinePlaysWhite:
14432       case MachinePlaysBlack:
14433       case TwoMachinesPlay:
14434       case IcsPlayingWhite:
14435       case IcsPlayingBlack:
14436       case AnalyzeMode:
14437       case AnalyzeFile:
14438         /* Skip if we know it isn't thinking */
14439         if (!cps->maybeThinking) return;
14440         if (appData.debugMode)
14441           fprintf(debugFP, "Interrupting %s\n", cps->which);
14442         InterruptChildProcess(cps->pr);
14443         cps->maybeThinking = FALSE;
14444         break;
14445       default:
14446         break;
14447     }
14448 #endif /*ATTENTION*/
14449 }
14450
14451 int
14452 CheckFlags()
14453 {
14454     if (whiteTimeRemaining <= 0) {
14455         if (!whiteFlag) {
14456             whiteFlag = TRUE;
14457             if (appData.icsActive) {
14458                 if (appData.autoCallFlag &&
14459                     gameMode == IcsPlayingBlack && !blackFlag) {
14460                   SendToICS(ics_prefix);
14461                   SendToICS("flag\n");
14462                 }
14463             } else {
14464                 if (blackFlag) {
14465                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14466                 } else {
14467                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14468                     if (appData.autoCallFlag) {
14469                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14470                         return TRUE;
14471                     }
14472                 }
14473             }
14474         }
14475     }
14476     if (blackTimeRemaining <= 0) {
14477         if (!blackFlag) {
14478             blackFlag = TRUE;
14479             if (appData.icsActive) {
14480                 if (appData.autoCallFlag &&
14481                     gameMode == IcsPlayingWhite && !whiteFlag) {
14482                   SendToICS(ics_prefix);
14483                   SendToICS("flag\n");
14484                 }
14485             } else {
14486                 if (whiteFlag) {
14487                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14488                 } else {
14489                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14490                     if (appData.autoCallFlag) {
14491                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14492                         return TRUE;
14493                     }
14494                 }
14495             }
14496         }
14497     }
14498     return FALSE;
14499 }
14500
14501 void
14502 CheckTimeControl()
14503 {
14504     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14505         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14506
14507     /*
14508      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14509      */
14510     if ( !WhiteOnMove(forwardMostMove) ) {
14511         /* White made time control */
14512         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14513         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14514         /* [HGM] time odds: correct new time quota for time odds! */
14515                                             / WhitePlayer()->timeOdds;
14516         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14517     } else {
14518         lastBlack -= blackTimeRemaining;
14519         /* Black made time control */
14520         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14521                                             / WhitePlayer()->other->timeOdds;
14522         lastWhite = whiteTimeRemaining;
14523     }
14524 }
14525
14526 void
14527 DisplayBothClocks()
14528 {
14529     int wom = gameMode == EditPosition ?
14530       !blackPlaysFirst : WhiteOnMove(currentMove);
14531     DisplayWhiteClock(whiteTimeRemaining, wom);
14532     DisplayBlackClock(blackTimeRemaining, !wom);
14533 }
14534
14535
14536 /* Timekeeping seems to be a portability nightmare.  I think everyone
14537    has ftime(), but I'm really not sure, so I'm including some ifdefs
14538    to use other calls if you don't.  Clocks will be less accurate if
14539    you have neither ftime nor gettimeofday.
14540 */
14541
14542 /* VS 2008 requires the #include outside of the function */
14543 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14544 #include <sys/timeb.h>
14545 #endif
14546
14547 /* Get the current time as a TimeMark */
14548 void
14549 GetTimeMark(tm)
14550      TimeMark *tm;
14551 {
14552 #if HAVE_GETTIMEOFDAY
14553
14554     struct timeval timeVal;
14555     struct timezone timeZone;
14556
14557     gettimeofday(&timeVal, &timeZone);
14558     tm->sec = (long) timeVal.tv_sec;
14559     tm->ms = (int) (timeVal.tv_usec / 1000L);
14560
14561 #else /*!HAVE_GETTIMEOFDAY*/
14562 #if HAVE_FTIME
14563
14564 // include <sys/timeb.h> / moved to just above start of function
14565     struct timeb timeB;
14566
14567     ftime(&timeB);
14568     tm->sec = (long) timeB.time;
14569     tm->ms = (int) timeB.millitm;
14570
14571 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14572     tm->sec = (long) time(NULL);
14573     tm->ms = 0;
14574 #endif
14575 #endif
14576 }
14577
14578 /* Return the difference in milliseconds between two
14579    time marks.  We assume the difference will fit in a long!
14580 */
14581 long
14582 SubtractTimeMarks(tm2, tm1)
14583      TimeMark *tm2, *tm1;
14584 {
14585     return 1000L*(tm2->sec - tm1->sec) +
14586            (long) (tm2->ms - tm1->ms);
14587 }
14588
14589
14590 /*
14591  * Code to manage the game clocks.
14592  *
14593  * In tournament play, black starts the clock and then white makes a move.
14594  * We give the human user a slight advantage if he is playing white---the
14595  * clocks don't run until he makes his first move, so it takes zero time.
14596  * Also, we don't account for network lag, so we could get out of sync
14597  * with GNU Chess's clock -- but then, referees are always right.
14598  */
14599
14600 static TimeMark tickStartTM;
14601 static long intendedTickLength;
14602
14603 long
14604 NextTickLength(timeRemaining)
14605      long timeRemaining;
14606 {
14607     long nominalTickLength, nextTickLength;
14608
14609     if (timeRemaining > 0L && timeRemaining <= 10000L)
14610       nominalTickLength = 100L;
14611     else
14612       nominalTickLength = 1000L;
14613     nextTickLength = timeRemaining % nominalTickLength;
14614     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14615
14616     return nextTickLength;
14617 }
14618
14619 /* Adjust clock one minute up or down */
14620 void
14621 AdjustClock(Boolean which, int dir)
14622 {
14623     if(which) blackTimeRemaining += 60000*dir;
14624     else      whiteTimeRemaining += 60000*dir;
14625     DisplayBothClocks();
14626 }
14627
14628 /* Stop clocks and reset to a fresh time control */
14629 void
14630 ResetClocks()
14631 {
14632     (void) StopClockTimer();
14633     if (appData.icsActive) {
14634         whiteTimeRemaining = blackTimeRemaining = 0;
14635     } else if (searchTime) {
14636         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14637         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14638     } else { /* [HGM] correct new time quote for time odds */
14639         whiteTC = blackTC = fullTimeControlString;
14640         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14641         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14642     }
14643     if (whiteFlag || blackFlag) {
14644         DisplayTitle("");
14645         whiteFlag = blackFlag = FALSE;
14646     }
14647     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14648     DisplayBothClocks();
14649 }
14650
14651 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14652
14653 /* Decrement running clock by amount of time that has passed */
14654 void
14655 DecrementClocks()
14656 {
14657     long timeRemaining;
14658     long lastTickLength, fudge;
14659     TimeMark now;
14660
14661     if (!appData.clockMode) return;
14662     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14663
14664     GetTimeMark(&now);
14665
14666     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14667
14668     /* Fudge if we woke up a little too soon */
14669     fudge = intendedTickLength - lastTickLength;
14670     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14671
14672     if (WhiteOnMove(forwardMostMove)) {
14673         if(whiteNPS >= 0) lastTickLength = 0;
14674         timeRemaining = whiteTimeRemaining -= lastTickLength;
14675         if(timeRemaining < 0 && !appData.icsActive) {
14676             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14677             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14678                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14679                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14680             }
14681         }
14682         DisplayWhiteClock(whiteTimeRemaining - fudge,
14683                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14684     } else {
14685         if(blackNPS >= 0) lastTickLength = 0;
14686         timeRemaining = blackTimeRemaining -= lastTickLength;
14687         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14688             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14689             if(suddenDeath) {
14690                 blackStartMove = forwardMostMove;
14691                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14692             }
14693         }
14694         DisplayBlackClock(blackTimeRemaining - fudge,
14695                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14696     }
14697     if (CheckFlags()) return;
14698
14699     tickStartTM = now;
14700     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14701     StartClockTimer(intendedTickLength);
14702
14703     /* if the time remaining has fallen below the alarm threshold, sound the
14704      * alarm. if the alarm has sounded and (due to a takeback or time control
14705      * with increment) the time remaining has increased to a level above the
14706      * threshold, reset the alarm so it can sound again.
14707      */
14708
14709     if (appData.icsActive && appData.icsAlarm) {
14710
14711         /* make sure we are dealing with the user's clock */
14712         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14713                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14714            )) return;
14715
14716         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14717             alarmSounded = FALSE;
14718         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14719             PlayAlarmSound();
14720             alarmSounded = TRUE;
14721         }
14722     }
14723 }
14724
14725
14726 /* A player has just moved, so stop the previously running
14727    clock and (if in clock mode) start the other one.
14728    We redisplay both clocks in case we're in ICS mode, because
14729    ICS gives us an update to both clocks after every move.
14730    Note that this routine is called *after* forwardMostMove
14731    is updated, so the last fractional tick must be subtracted
14732    from the color that is *not* on move now.
14733 */
14734 void
14735 SwitchClocks(int newMoveNr)
14736 {
14737     long lastTickLength;
14738     TimeMark now;
14739     int flagged = FALSE;
14740
14741     GetTimeMark(&now);
14742
14743     if (StopClockTimer() && appData.clockMode) {
14744         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14745         if (!WhiteOnMove(forwardMostMove)) {
14746             if(blackNPS >= 0) lastTickLength = 0;
14747             blackTimeRemaining -= lastTickLength;
14748            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14749 //         if(pvInfoList[forwardMostMove].time == -1)
14750                  pvInfoList[forwardMostMove].time =               // use GUI time
14751                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14752         } else {
14753            if(whiteNPS >= 0) lastTickLength = 0;
14754            whiteTimeRemaining -= lastTickLength;
14755            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14756 //         if(pvInfoList[forwardMostMove].time == -1)
14757                  pvInfoList[forwardMostMove].time =
14758                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14759         }
14760         flagged = CheckFlags();
14761     }
14762     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14763     CheckTimeControl();
14764
14765     if (flagged || !appData.clockMode) return;
14766
14767     switch (gameMode) {
14768       case MachinePlaysBlack:
14769       case MachinePlaysWhite:
14770       case BeginningOfGame:
14771         if (pausing) return;
14772         break;
14773
14774       case EditGame:
14775       case PlayFromGameFile:
14776       case IcsExamining:
14777         return;
14778
14779       default:
14780         break;
14781     }
14782
14783     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14784         if(WhiteOnMove(forwardMostMove))
14785              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14786         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14787     }
14788
14789     tickStartTM = now;
14790     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14791       whiteTimeRemaining : blackTimeRemaining);
14792     StartClockTimer(intendedTickLength);
14793 }
14794
14795
14796 /* Stop both clocks */
14797 void
14798 StopClocks()
14799 {
14800     long lastTickLength;
14801     TimeMark now;
14802
14803     if (!StopClockTimer()) return;
14804     if (!appData.clockMode) return;
14805
14806     GetTimeMark(&now);
14807
14808     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14809     if (WhiteOnMove(forwardMostMove)) {
14810         if(whiteNPS >= 0) lastTickLength = 0;
14811         whiteTimeRemaining -= lastTickLength;
14812         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14813     } else {
14814         if(blackNPS >= 0) lastTickLength = 0;
14815         blackTimeRemaining -= lastTickLength;
14816         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14817     }
14818     CheckFlags();
14819 }
14820
14821 /* Start clock of player on move.  Time may have been reset, so
14822    if clock is already running, stop and restart it. */
14823 void
14824 StartClocks()
14825 {
14826     (void) StopClockTimer(); /* in case it was running already */
14827     DisplayBothClocks();
14828     if (CheckFlags()) return;
14829
14830     if (!appData.clockMode) return;
14831     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14832
14833     GetTimeMark(&tickStartTM);
14834     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14835       whiteTimeRemaining : blackTimeRemaining);
14836
14837    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14838     whiteNPS = blackNPS = -1;
14839     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14840        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14841         whiteNPS = first.nps;
14842     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14843        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14844         blackNPS = first.nps;
14845     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14846         whiteNPS = second.nps;
14847     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14848         blackNPS = second.nps;
14849     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14850
14851     StartClockTimer(intendedTickLength);
14852 }
14853
14854 char *
14855 TimeString(ms)
14856      long ms;
14857 {
14858     long second, minute, hour, day;
14859     char *sign = "";
14860     static char buf[32];
14861
14862     if (ms > 0 && ms <= 9900) {
14863       /* convert milliseconds to tenths, rounding up */
14864       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14865
14866       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14867       return buf;
14868     }
14869
14870     /* convert milliseconds to seconds, rounding up */
14871     /* use floating point to avoid strangeness of integer division
14872        with negative dividends on many machines */
14873     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14874
14875     if (second < 0) {
14876         sign = "-";
14877         second = -second;
14878     }
14879
14880     day = second / (60 * 60 * 24);
14881     second = second % (60 * 60 * 24);
14882     hour = second / (60 * 60);
14883     second = second % (60 * 60);
14884     minute = second / 60;
14885     second = second % 60;
14886
14887     if (day > 0)
14888       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14889               sign, day, hour, minute, second);
14890     else if (hour > 0)
14891       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14892     else
14893       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14894
14895     return buf;
14896 }
14897
14898
14899 /*
14900  * This is necessary because some C libraries aren't ANSI C compliant yet.
14901  */
14902 char *
14903 StrStr(string, match)
14904      char *string, *match;
14905 {
14906     int i, length;
14907
14908     length = strlen(match);
14909
14910     for (i = strlen(string) - length; i >= 0; i--, string++)
14911       if (!strncmp(match, string, length))
14912         return string;
14913
14914     return NULL;
14915 }
14916
14917 char *
14918 StrCaseStr(string, match)
14919      char *string, *match;
14920 {
14921     int i, j, length;
14922
14923     length = strlen(match);
14924
14925     for (i = strlen(string) - length; i >= 0; i--, string++) {
14926         for (j = 0; j < length; j++) {
14927             if (ToLower(match[j]) != ToLower(string[j]))
14928               break;
14929         }
14930         if (j == length) return string;
14931     }
14932
14933     return NULL;
14934 }
14935
14936 #ifndef _amigados
14937 int
14938 StrCaseCmp(s1, s2)
14939      char *s1, *s2;
14940 {
14941     char c1, c2;
14942
14943     for (;;) {
14944         c1 = ToLower(*s1++);
14945         c2 = ToLower(*s2++);
14946         if (c1 > c2) return 1;
14947         if (c1 < c2) return -1;
14948         if (c1 == NULLCHAR) return 0;
14949     }
14950 }
14951
14952
14953 int
14954 ToLower(c)
14955      int c;
14956 {
14957     return isupper(c) ? tolower(c) : c;
14958 }
14959
14960
14961 int
14962 ToUpper(c)
14963      int c;
14964 {
14965     return islower(c) ? toupper(c) : c;
14966 }
14967 #endif /* !_amigados    */
14968
14969 char *
14970 StrSave(s)
14971      char *s;
14972 {
14973   char *ret;
14974
14975   if ((ret = (char *) malloc(strlen(s) + 1)))
14976     {
14977       safeStrCpy(ret, s, strlen(s)+1);
14978     }
14979   return ret;
14980 }
14981
14982 char *
14983 StrSavePtr(s, savePtr)
14984      char *s, **savePtr;
14985 {
14986     if (*savePtr) {
14987         free(*savePtr);
14988     }
14989     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14990       safeStrCpy(*savePtr, s, strlen(s)+1);
14991     }
14992     return(*savePtr);
14993 }
14994
14995 char *
14996 PGNDate()
14997 {
14998     time_t clock;
14999     struct tm *tm;
15000     char buf[MSG_SIZ];
15001
15002     clock = time((time_t *)NULL);
15003     tm = localtime(&clock);
15004     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15005             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15006     return StrSave(buf);
15007 }
15008
15009
15010 char *
15011 PositionToFEN(move, overrideCastling)
15012      int move;
15013      char *overrideCastling;
15014 {
15015     int i, j, fromX, fromY, toX, toY;
15016     int whiteToPlay;
15017     char buf[128];
15018     char *p, *q;
15019     int emptycount;
15020     ChessSquare piece;
15021
15022     whiteToPlay = (gameMode == EditPosition) ?
15023       !blackPlaysFirst : (move % 2 == 0);
15024     p = buf;
15025
15026     /* Piece placement data */
15027     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15028         emptycount = 0;
15029         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15030             if (boards[move][i][j] == EmptySquare) {
15031                 emptycount++;
15032             } else { ChessSquare piece = boards[move][i][j];
15033                 if (emptycount > 0) {
15034                     if(emptycount<10) /* [HGM] can be >= 10 */
15035                         *p++ = '0' + emptycount;
15036                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15037                     emptycount = 0;
15038                 }
15039                 if(PieceToChar(piece) == '+') {
15040                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15041                     *p++ = '+';
15042                     piece = (ChessSquare)(DEMOTED piece);
15043                 }
15044                 *p++ = PieceToChar(piece);
15045                 if(p[-1] == '~') {
15046                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15047                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15048                     *p++ = '~';
15049                 }
15050             }
15051         }
15052         if (emptycount > 0) {
15053             if(emptycount<10) /* [HGM] can be >= 10 */
15054                 *p++ = '0' + emptycount;
15055             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15056             emptycount = 0;
15057         }
15058         *p++ = '/';
15059     }
15060     *(p - 1) = ' ';
15061
15062     /* [HGM] print Crazyhouse or Shogi holdings */
15063     if( gameInfo.holdingsWidth ) {
15064         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15065         q = p;
15066         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15067             piece = boards[move][i][BOARD_WIDTH-1];
15068             if( piece != EmptySquare )
15069               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15070                   *p++ = PieceToChar(piece);
15071         }
15072         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15073             piece = boards[move][BOARD_HEIGHT-i-1][0];
15074             if( piece != EmptySquare )
15075               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15076                   *p++ = PieceToChar(piece);
15077         }
15078
15079         if( q == p ) *p++ = '-';
15080         *p++ = ']';
15081         *p++ = ' ';
15082     }
15083
15084     /* Active color */
15085     *p++ = whiteToPlay ? 'w' : 'b';
15086     *p++ = ' ';
15087
15088   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15089     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15090   } else {
15091   if(nrCastlingRights) {
15092      q = p;
15093      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15094        /* [HGM] write directly from rights */
15095            if(boards[move][CASTLING][2] != NoRights &&
15096               boards[move][CASTLING][0] != NoRights   )
15097                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15098            if(boards[move][CASTLING][2] != NoRights &&
15099               boards[move][CASTLING][1] != NoRights   )
15100                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15101            if(boards[move][CASTLING][5] != NoRights &&
15102               boards[move][CASTLING][3] != NoRights   )
15103                 *p++ = boards[move][CASTLING][3] + AAA;
15104            if(boards[move][CASTLING][5] != NoRights &&
15105               boards[move][CASTLING][4] != NoRights   )
15106                 *p++ = boards[move][CASTLING][4] + AAA;
15107      } else {
15108
15109         /* [HGM] write true castling rights */
15110         if( nrCastlingRights == 6 ) {
15111             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15112                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15113             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15114                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15115             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15116                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15117             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15118                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15119         }
15120      }
15121      if (q == p) *p++ = '-'; /* No castling rights */
15122      *p++ = ' ';
15123   }
15124
15125   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15126      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15127     /* En passant target square */
15128     if (move > backwardMostMove) {
15129         fromX = moveList[move - 1][0] - AAA;
15130         fromY = moveList[move - 1][1] - ONE;
15131         toX = moveList[move - 1][2] - AAA;
15132         toY = moveList[move - 1][3] - ONE;
15133         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15134             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15135             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15136             fromX == toX) {
15137             /* 2-square pawn move just happened */
15138             *p++ = toX + AAA;
15139             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15140         } else {
15141             *p++ = '-';
15142         }
15143     } else if(move == backwardMostMove) {
15144         // [HGM] perhaps we should always do it like this, and forget the above?
15145         if((signed char)boards[move][EP_STATUS] >= 0) {
15146             *p++ = boards[move][EP_STATUS] + AAA;
15147             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15148         } else {
15149             *p++ = '-';
15150         }
15151     } else {
15152         *p++ = '-';
15153     }
15154     *p++ = ' ';
15155   }
15156   }
15157
15158     /* [HGM] find reversible plies */
15159     {   int i = 0, j=move;
15160
15161         if (appData.debugMode) { int k;
15162             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15163             for(k=backwardMostMove; k<=forwardMostMove; k++)
15164                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15165
15166         }
15167
15168         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15169         if( j == backwardMostMove ) i += initialRulePlies;
15170         sprintf(p, "%d ", i);
15171         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15172     }
15173     /* Fullmove number */
15174     sprintf(p, "%d", (move / 2) + 1);
15175
15176     return StrSave(buf);
15177 }
15178
15179 Boolean
15180 ParseFEN(board, blackPlaysFirst, fen)
15181     Board board;
15182      int *blackPlaysFirst;
15183      char *fen;
15184 {
15185     int i, j;
15186     char *p, c;
15187     int emptycount;
15188     ChessSquare piece;
15189
15190     p = fen;
15191
15192     /* [HGM] by default clear Crazyhouse holdings, if present */
15193     if(gameInfo.holdingsWidth) {
15194        for(i=0; i<BOARD_HEIGHT; i++) {
15195            board[i][0]             = EmptySquare; /* black holdings */
15196            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15197            board[i][1]             = (ChessSquare) 0; /* black counts */
15198            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15199        }
15200     }
15201
15202     /* Piece placement data */
15203     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15204         j = 0;
15205         for (;;) {
15206             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15207                 if (*p == '/') p++;
15208                 emptycount = gameInfo.boardWidth - j;
15209                 while (emptycount--)
15210                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15211                 break;
15212 #if(BOARD_FILES >= 10)
15213             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15214                 p++; emptycount=10;
15215                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15216                 while (emptycount--)
15217                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15218 #endif
15219             } else if (isdigit(*p)) {
15220                 emptycount = *p++ - '0';
15221                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15222                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15223                 while (emptycount--)
15224                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15225             } else if (*p == '+' || isalpha(*p)) {
15226                 if (j >= gameInfo.boardWidth) return FALSE;
15227                 if(*p=='+') {
15228                     piece = CharToPiece(*++p);
15229                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15230                     piece = (ChessSquare) (PROMOTED piece ); p++;
15231                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15232                 } else piece = CharToPiece(*p++);
15233
15234                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15235                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15236                     piece = (ChessSquare) (PROMOTED piece);
15237                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15238                     p++;
15239                 }
15240                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15241             } else {
15242                 return FALSE;
15243             }
15244         }
15245     }
15246     while (*p == '/' || *p == ' ') p++;
15247
15248     /* [HGM] look for Crazyhouse holdings here */
15249     while(*p==' ') p++;
15250     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15251         if(*p == '[') p++;
15252         if(*p == '-' ) p++; /* empty holdings */ else {
15253             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15254             /* if we would allow FEN reading to set board size, we would   */
15255             /* have to add holdings and shift the board read so far here   */
15256             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15257                 p++;
15258                 if((int) piece >= (int) BlackPawn ) {
15259                     i = (int)piece - (int)BlackPawn;
15260                     i = PieceToNumber((ChessSquare)i);
15261                     if( i >= gameInfo.holdingsSize ) return FALSE;
15262                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15263                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15264                 } else {
15265                     i = (int)piece - (int)WhitePawn;
15266                     i = PieceToNumber((ChessSquare)i);
15267                     if( i >= gameInfo.holdingsSize ) return FALSE;
15268                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15269                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15270                 }
15271             }
15272         }
15273         if(*p == ']') p++;
15274     }
15275
15276     while(*p == ' ') p++;
15277
15278     /* Active color */
15279     c = *p++;
15280     if(appData.colorNickNames) {
15281       if( c == appData.colorNickNames[0] ) c = 'w'; else
15282       if( c == appData.colorNickNames[1] ) c = 'b';
15283     }
15284     switch (c) {
15285       case 'w':
15286         *blackPlaysFirst = FALSE;
15287         break;
15288       case 'b':
15289         *blackPlaysFirst = TRUE;
15290         break;
15291       default:
15292         return FALSE;
15293     }
15294
15295     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15296     /* return the extra info in global variiables             */
15297
15298     /* set defaults in case FEN is incomplete */
15299     board[EP_STATUS] = EP_UNKNOWN;
15300     for(i=0; i<nrCastlingRights; i++ ) {
15301         board[CASTLING][i] =
15302             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15303     }   /* assume possible unless obviously impossible */
15304     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15305     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15306     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15307                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15308     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15309     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15310     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15311                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15312     FENrulePlies = 0;
15313
15314     while(*p==' ') p++;
15315     if(nrCastlingRights) {
15316       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15317           /* castling indicator present, so default becomes no castlings */
15318           for(i=0; i<nrCastlingRights; i++ ) {
15319                  board[CASTLING][i] = NoRights;
15320           }
15321       }
15322       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15323              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15324              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15325              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15326         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15327
15328         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15329             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15330             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15331         }
15332         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15333             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15334         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15335                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15336         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15337                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15338         switch(c) {
15339           case'K':
15340               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15341               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15342               board[CASTLING][2] = whiteKingFile;
15343               break;
15344           case'Q':
15345               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15346               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15347               board[CASTLING][2] = whiteKingFile;
15348               break;
15349           case'k':
15350               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15351               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15352               board[CASTLING][5] = blackKingFile;
15353               break;
15354           case'q':
15355               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15356               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15357               board[CASTLING][5] = blackKingFile;
15358           case '-':
15359               break;
15360           default: /* FRC castlings */
15361               if(c >= 'a') { /* black rights */
15362                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15363                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15364                   if(i == BOARD_RGHT) break;
15365                   board[CASTLING][5] = i;
15366                   c -= AAA;
15367                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15368                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15369                   if(c > i)
15370                       board[CASTLING][3] = c;
15371                   else
15372                       board[CASTLING][4] = c;
15373               } else { /* white rights */
15374                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15375                     if(board[0][i] == WhiteKing) break;
15376                   if(i == BOARD_RGHT) break;
15377                   board[CASTLING][2] = i;
15378                   c -= AAA - 'a' + 'A';
15379                   if(board[0][c] >= WhiteKing) break;
15380                   if(c > i)
15381                       board[CASTLING][0] = c;
15382                   else
15383                       board[CASTLING][1] = c;
15384               }
15385         }
15386       }
15387       for(i=0; i<nrCastlingRights; i++)
15388         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15389     if (appData.debugMode) {
15390         fprintf(debugFP, "FEN castling rights:");
15391         for(i=0; i<nrCastlingRights; i++)
15392         fprintf(debugFP, " %d", board[CASTLING][i]);
15393         fprintf(debugFP, "\n");
15394     }
15395
15396       while(*p==' ') p++;
15397     }
15398
15399     /* read e.p. field in games that know e.p. capture */
15400     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15401        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15402       if(*p=='-') {
15403         p++; board[EP_STATUS] = EP_NONE;
15404       } else {
15405          char c = *p++ - AAA;
15406
15407          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15408          if(*p >= '0' && *p <='9') p++;
15409          board[EP_STATUS] = c;
15410       }
15411     }
15412
15413
15414     if(sscanf(p, "%d", &i) == 1) {
15415         FENrulePlies = i; /* 50-move ply counter */
15416         /* (The move number is still ignored)    */
15417     }
15418
15419     return TRUE;
15420 }
15421
15422 void
15423 EditPositionPasteFEN(char *fen)
15424 {
15425   if (fen != NULL) {
15426     Board initial_position;
15427
15428     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15429       DisplayError(_("Bad FEN position in clipboard"), 0);
15430       return ;
15431     } else {
15432       int savedBlackPlaysFirst = blackPlaysFirst;
15433       EditPositionEvent();
15434       blackPlaysFirst = savedBlackPlaysFirst;
15435       CopyBoard(boards[0], initial_position);
15436       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15437       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15438       DisplayBothClocks();
15439       DrawPosition(FALSE, boards[currentMove]);
15440     }
15441   }
15442 }
15443
15444 static char cseq[12] = "\\   ";
15445
15446 Boolean set_cont_sequence(char *new_seq)
15447 {
15448     int len;
15449     Boolean ret;
15450
15451     // handle bad attempts to set the sequence
15452         if (!new_seq)
15453                 return 0; // acceptable error - no debug
15454
15455     len = strlen(new_seq);
15456     ret = (len > 0) && (len < sizeof(cseq));
15457     if (ret)
15458       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15459     else if (appData.debugMode)
15460       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15461     return ret;
15462 }
15463
15464 /*
15465     reformat a source message so words don't cross the width boundary.  internal
15466     newlines are not removed.  returns the wrapped size (no null character unless
15467     included in source message).  If dest is NULL, only calculate the size required
15468     for the dest buffer.  lp argument indicats line position upon entry, and it's
15469     passed back upon exit.
15470 */
15471 int wrap(char *dest, char *src, int count, int width, int *lp)
15472 {
15473     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15474
15475     cseq_len = strlen(cseq);
15476     old_line = line = *lp;
15477     ansi = len = clen = 0;
15478
15479     for (i=0; i < count; i++)
15480     {
15481         if (src[i] == '\033')
15482             ansi = 1;
15483
15484         // if we hit the width, back up
15485         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15486         {
15487             // store i & len in case the word is too long
15488             old_i = i, old_len = len;
15489
15490             // find the end of the last word
15491             while (i && src[i] != ' ' && src[i] != '\n')
15492             {
15493                 i--;
15494                 len--;
15495             }
15496
15497             // word too long?  restore i & len before splitting it
15498             if ((old_i-i+clen) >= width)
15499             {
15500                 i = old_i;
15501                 len = old_len;
15502             }
15503
15504             // extra space?
15505             if (i && src[i-1] == ' ')
15506                 len--;
15507
15508             if (src[i] != ' ' && src[i] != '\n')
15509             {
15510                 i--;
15511                 if (len)
15512                     len--;
15513             }
15514
15515             // now append the newline and continuation sequence
15516             if (dest)
15517                 dest[len] = '\n';
15518             len++;
15519             if (dest)
15520                 strncpy(dest+len, cseq, cseq_len);
15521             len += cseq_len;
15522             line = cseq_len;
15523             clen = cseq_len;
15524             continue;
15525         }
15526
15527         if (dest)
15528             dest[len] = src[i];
15529         len++;
15530         if (!ansi)
15531             line++;
15532         if (src[i] == '\n')
15533             line = 0;
15534         if (src[i] == 'm')
15535             ansi = 0;
15536     }
15537     if (dest && appData.debugMode)
15538     {
15539         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15540             count, width, line, len, *lp);
15541         show_bytes(debugFP, src, count);
15542         fprintf(debugFP, "\ndest: ");
15543         show_bytes(debugFP, dest, len);
15544         fprintf(debugFP, "\n");
15545     }
15546     *lp = dest ? line : old_line;
15547
15548     return len;
15549 }
15550
15551 // [HGM] vari: routines for shelving variations
15552
15553 void
15554 PushTail(int firstMove, int lastMove)
15555 {
15556         int i, j, nrMoves = lastMove - firstMove;
15557
15558         if(appData.icsActive) { // only in local mode
15559                 forwardMostMove = currentMove; // mimic old ICS behavior
15560                 return;
15561         }
15562         if(storedGames >= MAX_VARIATIONS-1) return;
15563
15564         // push current tail of game on stack
15565         savedResult[storedGames] = gameInfo.result;
15566         savedDetails[storedGames] = gameInfo.resultDetails;
15567         gameInfo.resultDetails = NULL;
15568         savedFirst[storedGames] = firstMove;
15569         savedLast [storedGames] = lastMove;
15570         savedFramePtr[storedGames] = framePtr;
15571         framePtr -= nrMoves; // reserve space for the boards
15572         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15573             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15574             for(j=0; j<MOVE_LEN; j++)
15575                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15576             for(j=0; j<2*MOVE_LEN; j++)
15577                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15578             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15579             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15580             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15581             pvInfoList[firstMove+i-1].depth = 0;
15582             commentList[framePtr+i] = commentList[firstMove+i];
15583             commentList[firstMove+i] = NULL;
15584         }
15585
15586         storedGames++;
15587         forwardMostMove = firstMove; // truncate game so we can start variation
15588         if(storedGames == 1) GreyRevert(FALSE);
15589 }
15590
15591 Boolean
15592 PopTail(Boolean annotate)
15593 {
15594         int i, j, nrMoves;
15595         char buf[8000], moveBuf[20];
15596
15597         if(appData.icsActive) return FALSE; // only in local mode
15598         if(!storedGames) return FALSE; // sanity
15599         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15600
15601         storedGames--;
15602         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15603         nrMoves = savedLast[storedGames] - currentMove;
15604         if(annotate) {
15605                 int cnt = 10;
15606                 if(!WhiteOnMove(currentMove))
15607                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15608                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15609                 for(i=currentMove; i<forwardMostMove; i++) {
15610                         if(WhiteOnMove(i))
15611                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15612                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15613                         strcat(buf, moveBuf);
15614                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15615                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15616                 }
15617                 strcat(buf, ")");
15618         }
15619         for(i=1; i<=nrMoves; i++) { // copy last variation back
15620             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15621             for(j=0; j<MOVE_LEN; j++)
15622                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15623             for(j=0; j<2*MOVE_LEN; j++)
15624                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15625             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15626             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15627             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15628             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15629             commentList[currentMove+i] = commentList[framePtr+i];
15630             commentList[framePtr+i] = NULL;
15631         }
15632         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15633         framePtr = savedFramePtr[storedGames];
15634         gameInfo.result = savedResult[storedGames];
15635         if(gameInfo.resultDetails != NULL) {
15636             free(gameInfo.resultDetails);
15637       }
15638         gameInfo.resultDetails = savedDetails[storedGames];
15639         forwardMostMove = currentMove + nrMoves;
15640         if(storedGames == 0) GreyRevert(TRUE);
15641         return TRUE;
15642 }
15643
15644 void
15645 CleanupTail()
15646 {       // remove all shelved variations
15647         int i;
15648         for(i=0; i<storedGames; i++) {
15649             if(savedDetails[i])
15650                 free(savedDetails[i]);
15651             savedDetails[i] = NULL;
15652         }
15653         for(i=framePtr; i<MAX_MOVES; i++) {
15654                 if(commentList[i]) free(commentList[i]);
15655                 commentList[i] = NULL;
15656         }
15657         framePtr = MAX_MOVES-1;
15658         storedGames = 0;
15659 }
15660
15661 void
15662 LoadVariation(int index, char *text)
15663 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15664         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15665         int level = 0, move;
15666
15667         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15668         // first find outermost bracketing variation
15669         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15670             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15671                 if(*p == '{') wait = '}'; else
15672                 if(*p == '[') wait = ']'; else
15673                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15674                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15675             }
15676             if(*p == wait) wait = NULLCHAR; // closing ]} found
15677             p++;
15678         }
15679         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15680         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15681         end[1] = NULLCHAR; // clip off comment beyond variation
15682         ToNrEvent(currentMove-1);
15683         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15684         // kludge: use ParsePV() to append variation to game
15685         move = currentMove;
15686         ParsePV(start, TRUE);
15687         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15688         ClearPremoveHighlights();
15689         CommentPopDown();
15690         ToNrEvent(currentMove+1);
15691 }
15692