c0d36aa364926ad4eb8906b5a24719e9a3e3b2d0
[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, savePiece = EmptySquare;
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 InitBackEnd3 P((void))
1180 {
1181     GameMode initialMode;
1182     char buf[MSG_SIZ];
1183     int err, len;
1184
1185     InitChessProgram(&first, startedFromSetupPosition);
1186
1187     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1188         free(programVersion);
1189         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1190         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1191     }
1192
1193     if (appData.icsActive) {
1194 #ifdef WIN32
1195         /* [DM] Make a console window if needed [HGM] merged ifs */
1196         ConsoleCreate();
1197 #endif
1198         err = establish();
1199         if (err != 0)
1200           {
1201             if (*appData.icsCommPort != NULLCHAR)
1202               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1203                              appData.icsCommPort);
1204             else
1205               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1206                         appData.icsHost, appData.icsPort);
1207
1208             if( (len > MSG_SIZ) && appData.debugMode )
1209               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1210
1211             DisplayFatalError(buf, err, 1);
1212             return;
1213         }
1214         SetICSMode();
1215         telnetISR =
1216           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1217         fromUserISR =
1218           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1219         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1220             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1221     } else if (appData.noChessProgram) {
1222         SetNCPMode();
1223     } else {
1224         SetGNUMode();
1225     }
1226
1227     if (*appData.cmailGameName != NULLCHAR) {
1228         SetCmailMode();
1229         OpenLoopback(&cmailPR);
1230         cmailISR =
1231           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1232     }
1233
1234     ThawUI();
1235     DisplayMessage("", "");
1236     if (StrCaseCmp(appData.initialMode, "") == 0) {
1237       initialMode = BeginningOfGame;
1238     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1239       initialMode = TwoMachinesPlay;
1240     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1241       initialMode = AnalyzeFile;
1242     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1243       initialMode = AnalyzeMode;
1244     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1245       initialMode = MachinePlaysWhite;
1246     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1247       initialMode = MachinePlaysBlack;
1248     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1249       initialMode = EditGame;
1250     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1251       initialMode = EditPosition;
1252     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1253       initialMode = Training;
1254     } else {
1255       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1256       if( (len > MSG_SIZ) && appData.debugMode )
1257         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1258
1259       DisplayFatalError(buf, 0, 2);
1260       return;
1261     }
1262
1263     if (appData.matchMode) {
1264         /* Set up machine vs. machine match */
1265         if (appData.noChessProgram) {
1266             DisplayFatalError(_("Can't have a match with no chess programs"),
1267                               0, 2);
1268             return;
1269         }
1270         matchMode = TRUE;
1271         matchGame = 1;
1272         if (*appData.loadGameFile != NULLCHAR) {
1273             int index = appData.loadGameIndex; // [HGM] autoinc
1274             if(index<0) lastIndex = index = 1;
1275             if (!LoadGameFromFile(appData.loadGameFile,
1276                                   index,
1277                                   appData.loadGameFile, FALSE)) {
1278                 DisplayFatalError(_("Bad game file"), 0, 1);
1279                 return;
1280             }
1281         } else if (*appData.loadPositionFile != NULLCHAR) {
1282             int index = appData.loadPositionIndex; // [HGM] autoinc
1283             if(index<0) lastIndex = index = 1;
1284             if (!LoadPositionFromFile(appData.loadPositionFile,
1285                                       index,
1286                                       appData.loadPositionFile)) {
1287                 DisplayFatalError(_("Bad position file"), 0, 1);
1288                 return;
1289             }
1290         }
1291         TwoMachinesEvent();
1292     } else if (*appData.cmailGameName != NULLCHAR) {
1293         /* Set up cmail mode */
1294         ReloadCmailMsgEvent(TRUE);
1295     } else {
1296         /* Set up other modes */
1297         if (initialMode == AnalyzeFile) {
1298           if (*appData.loadGameFile == NULLCHAR) {
1299             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1300             return;
1301           }
1302         }
1303         if (*appData.loadGameFile != NULLCHAR) {
1304             (void) LoadGameFromFile(appData.loadGameFile,
1305                                     appData.loadGameIndex,
1306                                     appData.loadGameFile, TRUE);
1307         } else if (*appData.loadPositionFile != NULLCHAR) {
1308             (void) LoadPositionFromFile(appData.loadPositionFile,
1309                                         appData.loadPositionIndex,
1310                                         appData.loadPositionFile);
1311             /* [HGM] try to make self-starting even after FEN load */
1312             /* to allow automatic setup of fairy variants with wtm */
1313             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1314                 gameMode = BeginningOfGame;
1315                 setboardSpoiledMachineBlack = 1;
1316             }
1317             /* [HGM] loadPos: make that every new game uses the setup */
1318             /* from file as long as we do not switch variant          */
1319             if(!blackPlaysFirst) {
1320                 startedFromPositionFile = TRUE;
1321                 CopyBoard(filePosition, boards[0]);
1322             }
1323         }
1324         if (initialMode == AnalyzeMode) {
1325           if (appData.noChessProgram) {
1326             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1327             return;
1328           }
1329           if (appData.icsActive) {
1330             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1331             return;
1332           }
1333           AnalyzeModeEvent();
1334         } else if (initialMode == AnalyzeFile) {
1335           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1336           ShowThinkingEvent();
1337           AnalyzeFileEvent();
1338           AnalysisPeriodicEvent(1);
1339         } else if (initialMode == MachinePlaysWhite) {
1340           if (appData.noChessProgram) {
1341             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1342                               0, 2);
1343             return;
1344           }
1345           if (appData.icsActive) {
1346             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1347                               0, 2);
1348             return;
1349           }
1350           MachineWhiteEvent();
1351         } else if (initialMode == MachinePlaysBlack) {
1352           if (appData.noChessProgram) {
1353             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1354                               0, 2);
1355             return;
1356           }
1357           if (appData.icsActive) {
1358             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1359                               0, 2);
1360             return;
1361           }
1362           MachineBlackEvent();
1363         } else if (initialMode == TwoMachinesPlay) {
1364           if (appData.noChessProgram) {
1365             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1366                               0, 2);
1367             return;
1368           }
1369           if (appData.icsActive) {
1370             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1371                               0, 2);
1372             return;
1373           }
1374           TwoMachinesEvent();
1375         } else if (initialMode == EditGame) {
1376           EditGameEvent();
1377         } else if (initialMode == EditPosition) {
1378           EditPositionEvent();
1379         } else if (initialMode == Training) {
1380           if (*appData.loadGameFile == NULLCHAR) {
1381             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1382             return;
1383           }
1384           TrainingEvent();
1385         }
1386     }
1387 }
1388
1389 /*
1390  * Establish will establish a contact to a remote host.port.
1391  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1392  *  used to talk to the host.
1393  * Returns 0 if okay, error code if not.
1394  */
1395 int
1396 establish()
1397 {
1398     char buf[MSG_SIZ];
1399
1400     if (*appData.icsCommPort != NULLCHAR) {
1401         /* Talk to the host through a serial comm port */
1402         return OpenCommPort(appData.icsCommPort, &icsPR);
1403
1404     } else if (*appData.gateway != NULLCHAR) {
1405         if (*appData.remoteShell == NULLCHAR) {
1406             /* Use the rcmd protocol to run telnet program on a gateway host */
1407             snprintf(buf, sizeof(buf), "%s %s %s",
1408                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1409             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1410
1411         } else {
1412             /* Use the rsh program to run telnet program on a gateway host */
1413             if (*appData.remoteUser == NULLCHAR) {
1414                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1415                         appData.gateway, appData.telnetProgram,
1416                         appData.icsHost, appData.icsPort);
1417             } else {
1418                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1419                         appData.remoteShell, appData.gateway,
1420                         appData.remoteUser, appData.telnetProgram,
1421                         appData.icsHost, appData.icsPort);
1422             }
1423             return StartChildProcess(buf, "", &icsPR);
1424
1425         }
1426     } else if (appData.useTelnet) {
1427         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1428
1429     } else {
1430         /* TCP socket interface differs somewhat between
1431            Unix and NT; handle details in the front end.
1432            */
1433         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1434     }
1435 }
1436
1437 void EscapeExpand(char *p, char *q)
1438 {       // [HGM] initstring: routine to shape up string arguments
1439         while(*p++ = *q++) if(p[-1] == '\\')
1440             switch(*q++) {
1441                 case 'n': p[-1] = '\n'; break;
1442                 case 'r': p[-1] = '\r'; break;
1443                 case 't': p[-1] = '\t'; break;
1444                 case '\\': p[-1] = '\\'; break;
1445                 case 0: *p = 0; return;
1446                 default: p[-1] = q[-1]; break;
1447             }
1448 }
1449
1450 void
1451 show_bytes(fp, buf, count)
1452      FILE *fp;
1453      char *buf;
1454      int count;
1455 {
1456     while (count--) {
1457         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1458             fprintf(fp, "\\%03o", *buf & 0xff);
1459         } else {
1460             putc(*buf, fp);
1461         }
1462         buf++;
1463     }
1464     fflush(fp);
1465 }
1466
1467 /* Returns an errno value */
1468 int
1469 OutputMaybeTelnet(pr, message, count, outError)
1470      ProcRef pr;
1471      char *message;
1472      int count;
1473      int *outError;
1474 {
1475     char buf[8192], *p, *q, *buflim;
1476     int left, newcount, outcount;
1477
1478     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1479         *appData.gateway != NULLCHAR) {
1480         if (appData.debugMode) {
1481             fprintf(debugFP, ">ICS: ");
1482             show_bytes(debugFP, message, count);
1483             fprintf(debugFP, "\n");
1484         }
1485         return OutputToProcess(pr, message, count, outError);
1486     }
1487
1488     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1489     p = message;
1490     q = buf;
1491     left = count;
1492     newcount = 0;
1493     while (left) {
1494         if (q >= buflim) {
1495             if (appData.debugMode) {
1496                 fprintf(debugFP, ">ICS: ");
1497                 show_bytes(debugFP, buf, newcount);
1498                 fprintf(debugFP, "\n");
1499             }
1500             outcount = OutputToProcess(pr, buf, newcount, outError);
1501             if (outcount < newcount) return -1; /* to be sure */
1502             q = buf;
1503             newcount = 0;
1504         }
1505         if (*p == '\n') {
1506             *q++ = '\r';
1507             newcount++;
1508         } else if (((unsigned char) *p) == TN_IAC) {
1509             *q++ = (char) TN_IAC;
1510             newcount ++;
1511         }
1512         *q++ = *p++;
1513         newcount++;
1514         left--;
1515     }
1516     if (appData.debugMode) {
1517         fprintf(debugFP, ">ICS: ");
1518         show_bytes(debugFP, buf, newcount);
1519         fprintf(debugFP, "\n");
1520     }
1521     outcount = OutputToProcess(pr, buf, newcount, outError);
1522     if (outcount < newcount) return -1; /* to be sure */
1523     return count;
1524 }
1525
1526 void
1527 read_from_player(isr, closure, message, count, error)
1528      InputSourceRef isr;
1529      VOIDSTAR closure;
1530      char *message;
1531      int count;
1532      int error;
1533 {
1534     int outError, outCount;
1535     static int gotEof = 0;
1536
1537     /* Pass data read from player on to ICS */
1538     if (count > 0) {
1539         gotEof = 0;
1540         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1541         if (outCount < count) {
1542             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1543         }
1544     } else if (count < 0) {
1545         RemoveInputSource(isr);
1546         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1547     } else if (gotEof++ > 0) {
1548         RemoveInputSource(isr);
1549         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1550     }
1551 }
1552
1553 void
1554 KeepAlive()
1555 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1556     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1557     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1558     SendToICS("date\n");
1559     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1560 }
1561
1562 /* added routine for printf style output to ics */
1563 void ics_printf(char *format, ...)
1564 {
1565     char buffer[MSG_SIZ];
1566     va_list args;
1567
1568     va_start(args, format);
1569     vsnprintf(buffer, sizeof(buffer), format, args);
1570     buffer[sizeof(buffer)-1] = '\0';
1571     SendToICS(buffer);
1572     va_end(args);
1573 }
1574
1575 void
1576 SendToICS(s)
1577      char *s;
1578 {
1579     int count, outCount, outError;
1580
1581     if (icsPR == NULL) return;
1582
1583     count = strlen(s);
1584     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1585     if (outCount < count) {
1586         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1587     }
1588 }
1589
1590 /* This is used for sending logon scripts to the ICS. Sending
1591    without a delay causes problems when using timestamp on ICC
1592    (at least on my machine). */
1593 void
1594 SendToICSDelayed(s,msdelay)
1595      char *s;
1596      long msdelay;
1597 {
1598     int count, outCount, outError;
1599
1600     if (icsPR == NULL) return;
1601
1602     count = strlen(s);
1603     if (appData.debugMode) {
1604         fprintf(debugFP, ">ICS: ");
1605         show_bytes(debugFP, s, count);
1606         fprintf(debugFP, "\n");
1607     }
1608     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1609                                       msdelay);
1610     if (outCount < count) {
1611         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1612     }
1613 }
1614
1615
1616 /* Remove all highlighting escape sequences in s
1617    Also deletes any suffix starting with '('
1618    */
1619 char *
1620 StripHighlightAndTitle(s)
1621      char *s;
1622 {
1623     static char retbuf[MSG_SIZ];
1624     char *p = retbuf;
1625
1626     while (*s != NULLCHAR) {
1627         while (*s == '\033') {
1628             while (*s != NULLCHAR && !isalpha(*s)) s++;
1629             if (*s != NULLCHAR) s++;
1630         }
1631         while (*s != NULLCHAR && *s != '\033') {
1632             if (*s == '(' || *s == '[') {
1633                 *p = NULLCHAR;
1634                 return retbuf;
1635             }
1636             *p++ = *s++;
1637         }
1638     }
1639     *p = NULLCHAR;
1640     return retbuf;
1641 }
1642
1643 /* Remove all highlighting escape sequences in s */
1644 char *
1645 StripHighlight(s)
1646      char *s;
1647 {
1648     static char retbuf[MSG_SIZ];
1649     char *p = retbuf;
1650
1651     while (*s != NULLCHAR) {
1652         while (*s == '\033') {
1653             while (*s != NULLCHAR && !isalpha(*s)) s++;
1654             if (*s != NULLCHAR) s++;
1655         }
1656         while (*s != NULLCHAR && *s != '\033') {
1657             *p++ = *s++;
1658         }
1659     }
1660     *p = NULLCHAR;
1661     return retbuf;
1662 }
1663
1664 char *variantNames[] = VARIANT_NAMES;
1665 char *
1666 VariantName(v)
1667      VariantClass v;
1668 {
1669     return variantNames[v];
1670 }
1671
1672
1673 /* Identify a variant from the strings the chess servers use or the
1674    PGN Variant tag names we use. */
1675 VariantClass
1676 StringToVariant(e)
1677      char *e;
1678 {
1679     char *p;
1680     int wnum = -1;
1681     VariantClass v = VariantNormal;
1682     int i, found = FALSE;
1683     char buf[MSG_SIZ];
1684     int len;
1685
1686     if (!e) return v;
1687
1688     /* [HGM] skip over optional board-size prefixes */
1689     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1690         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1691         while( *e++ != '_');
1692     }
1693
1694     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1695         v = VariantNormal;
1696         found = TRUE;
1697     } else
1698     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1699       if (StrCaseStr(e, variantNames[i])) {
1700         v = (VariantClass) i;
1701         found = TRUE;
1702         break;
1703       }
1704     }
1705
1706     if (!found) {
1707       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1708           || StrCaseStr(e, "wild/fr")
1709           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1710         v = VariantFischeRandom;
1711       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1712                  (i = 1, p = StrCaseStr(e, "w"))) {
1713         p += i;
1714         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1715         if (isdigit(*p)) {
1716           wnum = atoi(p);
1717         } else {
1718           wnum = -1;
1719         }
1720         switch (wnum) {
1721         case 0: /* FICS only, actually */
1722         case 1:
1723           /* Castling legal even if K starts on d-file */
1724           v = VariantWildCastle;
1725           break;
1726         case 2:
1727         case 3:
1728         case 4:
1729           /* Castling illegal even if K & R happen to start in
1730              normal positions. */
1731           v = VariantNoCastle;
1732           break;
1733         case 5:
1734         case 7:
1735         case 8:
1736         case 10:
1737         case 11:
1738         case 12:
1739         case 13:
1740         case 14:
1741         case 15:
1742         case 18:
1743         case 19:
1744           /* Castling legal iff K & R start in normal positions */
1745           v = VariantNormal;
1746           break;
1747         case 6:
1748         case 20:
1749         case 21:
1750           /* Special wilds for position setup; unclear what to do here */
1751           v = VariantLoadable;
1752           break;
1753         case 9:
1754           /* Bizarre ICC game */
1755           v = VariantTwoKings;
1756           break;
1757         case 16:
1758           v = VariantKriegspiel;
1759           break;
1760         case 17:
1761           v = VariantLosers;
1762           break;
1763         case 22:
1764           v = VariantFischeRandom;
1765           break;
1766         case 23:
1767           v = VariantCrazyhouse;
1768           break;
1769         case 24:
1770           v = VariantBughouse;
1771           break;
1772         case 25:
1773           v = Variant3Check;
1774           break;
1775         case 26:
1776           /* Not quite the same as FICS suicide! */
1777           v = VariantGiveaway;
1778           break;
1779         case 27:
1780           v = VariantAtomic;
1781           break;
1782         case 28:
1783           v = VariantShatranj;
1784           break;
1785
1786         /* Temporary names for future ICC types.  The name *will* change in
1787            the next xboard/WinBoard release after ICC defines it. */
1788         case 29:
1789           v = Variant29;
1790           break;
1791         case 30:
1792           v = Variant30;
1793           break;
1794         case 31:
1795           v = Variant31;
1796           break;
1797         case 32:
1798           v = Variant32;
1799           break;
1800         case 33:
1801           v = Variant33;
1802           break;
1803         case 34:
1804           v = Variant34;
1805           break;
1806         case 35:
1807           v = Variant35;
1808           break;
1809         case 36:
1810           v = Variant36;
1811           break;
1812         case 37:
1813           v = VariantShogi;
1814           break;
1815         case 38:
1816           v = VariantXiangqi;
1817           break;
1818         case 39:
1819           v = VariantCourier;
1820           break;
1821         case 40:
1822           v = VariantGothic;
1823           break;
1824         case 41:
1825           v = VariantCapablanca;
1826           break;
1827         case 42:
1828           v = VariantKnightmate;
1829           break;
1830         case 43:
1831           v = VariantFairy;
1832           break;
1833         case 44:
1834           v = VariantCylinder;
1835           break;
1836         case 45:
1837           v = VariantFalcon;
1838           break;
1839         case 46:
1840           v = VariantCapaRandom;
1841           break;
1842         case 47:
1843           v = VariantBerolina;
1844           break;
1845         case 48:
1846           v = VariantJanus;
1847           break;
1848         case 49:
1849           v = VariantSuper;
1850           break;
1851         case 50:
1852           v = VariantGreat;
1853           break;
1854         case -1:
1855           /* Found "wild" or "w" in the string but no number;
1856              must assume it's normal chess. */
1857           v = VariantNormal;
1858           break;
1859         default:
1860           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1861           if( (len > MSG_SIZ) && appData.debugMode )
1862             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1863
1864           DisplayError(buf, 0);
1865           v = VariantUnknown;
1866           break;
1867         }
1868       }
1869     }
1870     if (appData.debugMode) {
1871       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1872               e, wnum, VariantName(v));
1873     }
1874     return v;
1875 }
1876
1877 static int leftover_start = 0, leftover_len = 0;
1878 char star_match[STAR_MATCH_N][MSG_SIZ];
1879
1880 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1881    advance *index beyond it, and set leftover_start to the new value of
1882    *index; else return FALSE.  If pattern contains the character '*', it
1883    matches any sequence of characters not containing '\r', '\n', or the
1884    character following the '*' (if any), and the matched sequence(s) are
1885    copied into star_match.
1886    */
1887 int
1888 looking_at(buf, index, pattern)
1889      char *buf;
1890      int *index;
1891      char *pattern;
1892 {
1893     char *bufp = &buf[*index], *patternp = pattern;
1894     int star_count = 0;
1895     char *matchp = star_match[0];
1896
1897     for (;;) {
1898         if (*patternp == NULLCHAR) {
1899             *index = leftover_start = bufp - buf;
1900             *matchp = NULLCHAR;
1901             return TRUE;
1902         }
1903         if (*bufp == NULLCHAR) return FALSE;
1904         if (*patternp == '*') {
1905             if (*bufp == *(patternp + 1)) {
1906                 *matchp = NULLCHAR;
1907                 matchp = star_match[++star_count];
1908                 patternp += 2;
1909                 bufp++;
1910                 continue;
1911             } else if (*bufp == '\n' || *bufp == '\r') {
1912                 patternp++;
1913                 if (*patternp == NULLCHAR)
1914                   continue;
1915                 else
1916                   return FALSE;
1917             } else {
1918                 *matchp++ = *bufp++;
1919                 continue;
1920             }
1921         }
1922         if (*patternp != *bufp) return FALSE;
1923         patternp++;
1924         bufp++;
1925     }
1926 }
1927
1928 void
1929 SendToPlayer(data, length)
1930      char *data;
1931      int length;
1932 {
1933     int error, outCount;
1934     outCount = OutputToProcess(NoProc, data, length, &error);
1935     if (outCount < length) {
1936         DisplayFatalError(_("Error writing to display"), error, 1);
1937     }
1938 }
1939
1940 void
1941 PackHolding(packed, holding)
1942      char packed[];
1943      char *holding;
1944 {
1945     char *p = holding;
1946     char *q = packed;
1947     int runlength = 0;
1948     int curr = 9999;
1949     do {
1950         if (*p == curr) {
1951             runlength++;
1952         } else {
1953             switch (runlength) {
1954               case 0:
1955                 break;
1956               case 1:
1957                 *q++ = curr;
1958                 break;
1959               case 2:
1960                 *q++ = curr;
1961                 *q++ = curr;
1962                 break;
1963               default:
1964                 sprintf(q, "%d", runlength);
1965                 while (*q) q++;
1966                 *q++ = curr;
1967                 break;
1968             }
1969             runlength = 1;
1970             curr = *p;
1971         }
1972     } while (*p++);
1973     *q = NULLCHAR;
1974 }
1975
1976 /* Telnet protocol requests from the front end */
1977 void
1978 TelnetRequest(ddww, option)
1979      unsigned char ddww, option;
1980 {
1981     unsigned char msg[3];
1982     int outCount, outError;
1983
1984     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1985
1986     if (appData.debugMode) {
1987         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1988         switch (ddww) {
1989           case TN_DO:
1990             ddwwStr = "DO";
1991             break;
1992           case TN_DONT:
1993             ddwwStr = "DONT";
1994             break;
1995           case TN_WILL:
1996             ddwwStr = "WILL";
1997             break;
1998           case TN_WONT:
1999             ddwwStr = "WONT";
2000             break;
2001           default:
2002             ddwwStr = buf1;
2003             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2004             break;
2005         }
2006         switch (option) {
2007           case TN_ECHO:
2008             optionStr = "ECHO";
2009             break;
2010           default:
2011             optionStr = buf2;
2012             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2013             break;
2014         }
2015         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2016     }
2017     msg[0] = TN_IAC;
2018     msg[1] = ddww;
2019     msg[2] = option;
2020     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2021     if (outCount < 3) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 void
2027 DoEcho()
2028 {
2029     if (!appData.icsActive) return;
2030     TelnetRequest(TN_DO, TN_ECHO);
2031 }
2032
2033 void
2034 DontEcho()
2035 {
2036     if (!appData.icsActive) return;
2037     TelnetRequest(TN_DONT, TN_ECHO);
2038 }
2039
2040 void
2041 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2042 {
2043     /* put the holdings sent to us by the server on the board holdings area */
2044     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2045     char p;
2046     ChessSquare piece;
2047
2048     if(gameInfo.holdingsWidth < 2)  return;
2049     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2050         return; // prevent overwriting by pre-board holdings
2051
2052     if( (int)lowestPiece >= BlackPawn ) {
2053         holdingsColumn = 0;
2054         countsColumn = 1;
2055         holdingsStartRow = BOARD_HEIGHT-1;
2056         direction = -1;
2057     } else {
2058         holdingsColumn = BOARD_WIDTH-1;
2059         countsColumn = BOARD_WIDTH-2;
2060         holdingsStartRow = 0;
2061         direction = 1;
2062     }
2063
2064     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2065         board[i][holdingsColumn] = EmptySquare;
2066         board[i][countsColumn]   = (ChessSquare) 0;
2067     }
2068     while( (p=*holdings++) != NULLCHAR ) {
2069         piece = CharToPiece( ToUpper(p) );
2070         if(piece == EmptySquare) continue;
2071         /*j = (int) piece - (int) WhitePawn;*/
2072         j = PieceToNumber(piece);
2073         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2074         if(j < 0) continue;               /* should not happen */
2075         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2076         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2077         board[holdingsStartRow+j*direction][countsColumn]++;
2078     }
2079 }
2080
2081
2082 void
2083 VariantSwitch(Board board, VariantClass newVariant)
2084 {
2085    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2086    static Board oldBoard;
2087
2088    startedFromPositionFile = FALSE;
2089    if(gameInfo.variant == newVariant) return;
2090
2091    /* [HGM] This routine is called each time an assignment is made to
2092     * gameInfo.variant during a game, to make sure the board sizes
2093     * are set to match the new variant. If that means adding or deleting
2094     * holdings, we shift the playing board accordingly
2095     * This kludge is needed because in ICS observe mode, we get boards
2096     * of an ongoing game without knowing the variant, and learn about the
2097     * latter only later. This can be because of the move list we requested,
2098     * in which case the game history is refilled from the beginning anyway,
2099     * but also when receiving holdings of a crazyhouse game. In the latter
2100     * case we want to add those holdings to the already received position.
2101     */
2102
2103
2104    if (appData.debugMode) {
2105      fprintf(debugFP, "Switch board from %s to %s\n",
2106              VariantName(gameInfo.variant), VariantName(newVariant));
2107      setbuf(debugFP, NULL);
2108    }
2109    shuffleOpenings = 0;       /* [HGM] shuffle */
2110    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2111    switch(newVariant)
2112      {
2113      case VariantShogi:
2114        newWidth = 9;  newHeight = 9;
2115        gameInfo.holdingsSize = 7;
2116      case VariantBughouse:
2117      case VariantCrazyhouse:
2118        newHoldingsWidth = 2; break;
2119      case VariantGreat:
2120        newWidth = 10;
2121      case VariantSuper:
2122        newHoldingsWidth = 2;
2123        gameInfo.holdingsSize = 8;
2124        break;
2125      case VariantGothic:
2126      case VariantCapablanca:
2127      case VariantCapaRandom:
2128        newWidth = 10;
2129      default:
2130        newHoldingsWidth = gameInfo.holdingsSize = 0;
2131      };
2132
2133    if(newWidth  != gameInfo.boardWidth  ||
2134       newHeight != gameInfo.boardHeight ||
2135       newHoldingsWidth != gameInfo.holdingsWidth ) {
2136
2137      /* shift position to new playing area, if needed */
2138      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2139        for(i=0; i<BOARD_HEIGHT; i++)
2140          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2141            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2142              board[i][j];
2143        for(i=0; i<newHeight; i++) {
2144          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2145          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2146        }
2147      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2148        for(i=0; i<BOARD_HEIGHT; i++)
2149          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2150            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2151              board[i][j];
2152      }
2153      gameInfo.boardWidth  = newWidth;
2154      gameInfo.boardHeight = newHeight;
2155      gameInfo.holdingsWidth = newHoldingsWidth;
2156      gameInfo.variant = newVariant;
2157      InitDrawingSizes(-2, 0);
2158    } else gameInfo.variant = newVariant;
2159    CopyBoard(oldBoard, board);   // remember correctly formatted board
2160      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2161    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2162 }
2163
2164 static int loggedOn = FALSE;
2165
2166 /*-- Game start info cache: --*/
2167 int gs_gamenum;
2168 char gs_kind[MSG_SIZ];
2169 static char player1Name[128] = "";
2170 static char player2Name[128] = "";
2171 static char cont_seq[] = "\n\\   ";
2172 static int player1Rating = -1;
2173 static int player2Rating = -1;
2174 /*----------------------------*/
2175
2176 ColorClass curColor = ColorNormal;
2177 int suppressKibitz = 0;
2178
2179 // [HGM] seekgraph
2180 Boolean soughtPending = FALSE;
2181 Boolean seekGraphUp;
2182 #define MAX_SEEK_ADS 200
2183 #define SQUARE 0x80
2184 char *seekAdList[MAX_SEEK_ADS];
2185 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2186 float tcList[MAX_SEEK_ADS];
2187 char colorList[MAX_SEEK_ADS];
2188 int nrOfSeekAds = 0;
2189 int minRating = 1010, maxRating = 2800;
2190 int hMargin = 10, vMargin = 20, h, w;
2191 extern int squareSize, lineGap;
2192
2193 void
2194 PlotSeekAd(int i)
2195 {
2196         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2197         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2198         if(r < minRating+100 && r >=0 ) r = minRating+100;
2199         if(r > maxRating) r = maxRating;
2200         if(tc < 1.) tc = 1.;
2201         if(tc > 95.) tc = 95.;
2202         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2203         y = ((double)r - minRating)/(maxRating - minRating)
2204             * (h-vMargin-squareSize/8-1) + vMargin;
2205         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2206         if(strstr(seekAdList[i], " u ")) color = 1;
2207         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2208            !strstr(seekAdList[i], "bullet") &&
2209            !strstr(seekAdList[i], "blitz") &&
2210            !strstr(seekAdList[i], "standard") ) color = 2;
2211         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2212         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2213 }
2214
2215 void
2216 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2217 {
2218         char buf[MSG_SIZ], *ext = "";
2219         VariantClass v = StringToVariant(type);
2220         if(strstr(type, "wild")) {
2221             ext = type + 4; // append wild number
2222             if(v == VariantFischeRandom) type = "chess960"; else
2223             if(v == VariantLoadable) type = "setup"; else
2224             type = VariantName(v);
2225         }
2226         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2227         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2228             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2229             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2230             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2231             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2232             seekNrList[nrOfSeekAds] = nr;
2233             zList[nrOfSeekAds] = 0;
2234             seekAdList[nrOfSeekAds++] = StrSave(buf);
2235             if(plot) PlotSeekAd(nrOfSeekAds-1);
2236         }
2237 }
2238
2239 void
2240 EraseSeekDot(int i)
2241 {
2242     int x = xList[i], y = yList[i], d=squareSize/4, k;
2243     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2244     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2245     // now replot every dot that overlapped
2246     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2247         int xx = xList[k], yy = yList[k];
2248         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2249             DrawSeekDot(xx, yy, colorList[k]);
2250     }
2251 }
2252
2253 void
2254 RemoveSeekAd(int nr)
2255 {
2256         int i;
2257         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2258             EraseSeekDot(i);
2259             if(seekAdList[i]) free(seekAdList[i]);
2260             seekAdList[i] = seekAdList[--nrOfSeekAds];
2261             seekNrList[i] = seekNrList[nrOfSeekAds];
2262             ratingList[i] = ratingList[nrOfSeekAds];
2263             colorList[i]  = colorList[nrOfSeekAds];
2264             tcList[i] = tcList[nrOfSeekAds];
2265             xList[i]  = xList[nrOfSeekAds];
2266             yList[i]  = yList[nrOfSeekAds];
2267             zList[i]  = zList[nrOfSeekAds];
2268             seekAdList[nrOfSeekAds] = NULL;
2269             break;
2270         }
2271 }
2272
2273 Boolean
2274 MatchSoughtLine(char *line)
2275 {
2276     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2277     int nr, base, inc, u=0; char dummy;
2278
2279     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2281        (u=1) &&
2282        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2283         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2284         // match: compact and save the line
2285         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2286         return TRUE;
2287     }
2288     return FALSE;
2289 }
2290
2291 int
2292 DrawSeekGraph()
2293 {
2294     int i;
2295     if(!seekGraphUp) return FALSE;
2296     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2297     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2298
2299     DrawSeekBackground(0, 0, w, h);
2300     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2301     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2302     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2303         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2304         yy = h-1-yy;
2305         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2306         if(i%500 == 0) {
2307             char buf[MSG_SIZ];
2308             snprintf(buf, MSG_SIZ, "%d", i);
2309             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2310         }
2311     }
2312     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2313     for(i=1; i<100; i+=(i<10?1:5)) {
2314         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2315         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2316         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2317             char buf[MSG_SIZ];
2318             snprintf(buf, MSG_SIZ, "%d", i);
2319             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2320         }
2321     }
2322     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2323     return TRUE;
2324 }
2325
2326 int SeekGraphClick(ClickType click, int x, int y, int moving)
2327 {
2328     static int lastDown = 0, displayed = 0, lastSecond;
2329     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2330         if(click == Release || moving) return FALSE;
2331         nrOfSeekAds = 0;
2332         soughtPending = TRUE;
2333         SendToICS(ics_prefix);
2334         SendToICS("sought\n"); // should this be "sought all"?
2335     } else { // issue challenge based on clicked ad
2336         int dist = 10000; int i, closest = 0, second = 0;
2337         for(i=0; i<nrOfSeekAds; i++) {
2338             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2339             if(d < dist) { dist = d; closest = i; }
2340             second += (d - zList[i] < 120); // count in-range ads
2341             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2342         }
2343         if(dist < 120) {
2344             char buf[MSG_SIZ];
2345             second = (second > 1);
2346             if(displayed != closest || second != lastSecond) {
2347                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2348                 lastSecond = second; displayed = closest;
2349             }
2350             if(click == Press) {
2351                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2352                 lastDown = closest;
2353                 return TRUE;
2354             } // on press 'hit', only show info
2355             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2356             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2357             SendToICS(ics_prefix);
2358             SendToICS(buf);
2359             return TRUE; // let incoming board of started game pop down the graph
2360         } else if(click == Release) { // release 'miss' is ignored
2361             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2362             if(moving == 2) { // right up-click
2363                 nrOfSeekAds = 0; // refresh graph
2364                 soughtPending = TRUE;
2365                 SendToICS(ics_prefix);
2366                 SendToICS("sought\n"); // should this be "sought all"?
2367             }
2368             return TRUE;
2369         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2370         // press miss or release hit 'pop down' seek graph
2371         seekGraphUp = FALSE;
2372         DrawPosition(TRUE, NULL);
2373     }
2374     return TRUE;
2375 }
2376
2377 void
2378 read_from_ics(isr, closure, data, count, error)
2379      InputSourceRef isr;
2380      VOIDSTAR closure;
2381      char *data;
2382      int count;
2383      int error;
2384 {
2385 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2386 #define STARTED_NONE 0
2387 #define STARTED_MOVES 1
2388 #define STARTED_BOARD 2
2389 #define STARTED_OBSERVE 3
2390 #define STARTED_HOLDINGS 4
2391 #define STARTED_CHATTER 5
2392 #define STARTED_COMMENT 6
2393 #define STARTED_MOVES_NOHIDE 7
2394
2395     static int started = STARTED_NONE;
2396     static char parse[20000];
2397     static int parse_pos = 0;
2398     static char buf[BUF_SIZE + 1];
2399     static int firstTime = TRUE, intfSet = FALSE;
2400     static ColorClass prevColor = ColorNormal;
2401     static int savingComment = FALSE;
2402     static int cmatch = 0; // continuation sequence match
2403     char *bp;
2404     char str[MSG_SIZ];
2405     int i, oldi;
2406     int buf_len;
2407     int next_out;
2408     int tkind;
2409     int backup;    /* [DM] For zippy color lines */
2410     char *p;
2411     char talker[MSG_SIZ]; // [HGM] chat
2412     int channel;
2413
2414     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2415
2416     if (appData.debugMode) {
2417       if (!error) {
2418         fprintf(debugFP, "<ICS: ");
2419         show_bytes(debugFP, data, count);
2420         fprintf(debugFP, "\n");
2421       }
2422     }
2423
2424     if (appData.debugMode) { int f = forwardMostMove;
2425         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2426                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2427                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2428     }
2429     if (count > 0) {
2430         /* If last read ended with a partial line that we couldn't parse,
2431            prepend it to the new read and try again. */
2432         if (leftover_len > 0) {
2433             for (i=0; i<leftover_len; i++)
2434               buf[i] = buf[leftover_start + i];
2435         }
2436
2437     /* copy new characters into the buffer */
2438     bp = buf + leftover_len;
2439     buf_len=leftover_len;
2440     for (i=0; i<count; i++)
2441     {
2442         // ignore these
2443         if (data[i] == '\r')
2444             continue;
2445
2446         // join lines split by ICS?
2447         if (!appData.noJoin)
2448         {
2449             /*
2450                 Joining just consists of finding matches against the
2451                 continuation sequence, and discarding that sequence
2452                 if found instead of copying it.  So, until a match
2453                 fails, there's nothing to do since it might be the
2454                 complete sequence, and thus, something we don't want
2455                 copied.
2456             */
2457             if (data[i] == cont_seq[cmatch])
2458             {
2459                 cmatch++;
2460                 if (cmatch == strlen(cont_seq))
2461                 {
2462                     cmatch = 0; // complete match.  just reset the counter
2463
2464                     /*
2465                         it's possible for the ICS to not include the space
2466                         at the end of the last word, making our [correct]
2467                         join operation fuse two separate words.  the server
2468                         does this when the space occurs at the width setting.
2469                     */
2470                     if (!buf_len || buf[buf_len-1] != ' ')
2471                     {
2472                         *bp++ = ' ';
2473                         buf_len++;
2474                     }
2475                 }
2476                 continue;
2477             }
2478             else if (cmatch)
2479             {
2480                 /*
2481                     match failed, so we have to copy what matched before
2482                     falling through and copying this character.  In reality,
2483                     this will only ever be just the newline character, but
2484                     it doesn't hurt to be precise.
2485                 */
2486                 strncpy(bp, cont_seq, cmatch);
2487                 bp += cmatch;
2488                 buf_len += cmatch;
2489                 cmatch = 0;
2490             }
2491         }
2492
2493         // copy this char
2494         *bp++ = data[i];
2495         buf_len++;
2496     }
2497
2498         buf[buf_len] = NULLCHAR;
2499 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2500         next_out = 0;
2501         leftover_start = 0;
2502
2503         i = 0;
2504         while (i < buf_len) {
2505             /* Deal with part of the TELNET option negotiation
2506                protocol.  We refuse to do anything beyond the
2507                defaults, except that we allow the WILL ECHO option,
2508                which ICS uses to turn off password echoing when we are
2509                directly connected to it.  We reject this option
2510                if localLineEditing mode is on (always on in xboard)
2511                and we are talking to port 23, which might be a real
2512                telnet server that will try to keep WILL ECHO on permanently.
2513              */
2514             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2515                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2516                 unsigned char option;
2517                 oldi = i;
2518                 switch ((unsigned char) buf[++i]) {
2519                   case TN_WILL:
2520                     if (appData.debugMode)
2521                       fprintf(debugFP, "\n<WILL ");
2522                     switch (option = (unsigned char) buf[++i]) {
2523                       case TN_ECHO:
2524                         if (appData.debugMode)
2525                           fprintf(debugFP, "ECHO ");
2526                         /* Reply only if this is a change, according
2527                            to the protocol rules. */
2528                         if (remoteEchoOption) break;
2529                         if (appData.localLineEditing &&
2530                             atoi(appData.icsPort) == TN_PORT) {
2531                             TelnetRequest(TN_DONT, TN_ECHO);
2532                         } else {
2533                             EchoOff();
2534                             TelnetRequest(TN_DO, TN_ECHO);
2535                             remoteEchoOption = TRUE;
2536                         }
2537                         break;
2538                       default:
2539                         if (appData.debugMode)
2540                           fprintf(debugFP, "%d ", option);
2541                         /* Whatever this is, we don't want it. */
2542                         TelnetRequest(TN_DONT, option);
2543                         break;
2544                     }
2545                     break;
2546                   case TN_WONT:
2547                     if (appData.debugMode)
2548                       fprintf(debugFP, "\n<WONT ");
2549                     switch (option = (unsigned char) buf[++i]) {
2550                       case TN_ECHO:
2551                         if (appData.debugMode)
2552                           fprintf(debugFP, "ECHO ");
2553                         /* Reply only if this is a change, according
2554                            to the protocol rules. */
2555                         if (!remoteEchoOption) break;
2556                         EchoOn();
2557                         TelnetRequest(TN_DONT, TN_ECHO);
2558                         remoteEchoOption = FALSE;
2559                         break;
2560                       default:
2561                         if (appData.debugMode)
2562                           fprintf(debugFP, "%d ", (unsigned char) option);
2563                         /* Whatever this is, it must already be turned
2564                            off, because we never agree to turn on
2565                            anything non-default, so according to the
2566                            protocol rules, we don't reply. */
2567                         break;
2568                     }
2569                     break;
2570                   case TN_DO:
2571                     if (appData.debugMode)
2572                       fprintf(debugFP, "\n<DO ");
2573                     switch (option = (unsigned char) buf[++i]) {
2574                       default:
2575                         /* Whatever this is, we refuse to do it. */
2576                         if (appData.debugMode)
2577                           fprintf(debugFP, "%d ", option);
2578                         TelnetRequest(TN_WONT, option);
2579                         break;
2580                     }
2581                     break;
2582                   case TN_DONT:
2583                     if (appData.debugMode)
2584                       fprintf(debugFP, "\n<DONT ");
2585                     switch (option = (unsigned char) buf[++i]) {
2586                       default:
2587                         if (appData.debugMode)
2588                           fprintf(debugFP, "%d ", option);
2589                         /* Whatever this is, we are already not doing
2590                            it, because we never agree to do anything
2591                            non-default, so according to the protocol
2592                            rules, we don't reply. */
2593                         break;
2594                     }
2595                     break;
2596                   case TN_IAC:
2597                     if (appData.debugMode)
2598                       fprintf(debugFP, "\n<IAC ");
2599                     /* Doubled IAC; pass it through */
2600                     i--;
2601                     break;
2602                   default:
2603                     if (appData.debugMode)
2604                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2605                     /* Drop all other telnet commands on the floor */
2606                     break;
2607                 }
2608                 if (oldi > next_out)
2609                   SendToPlayer(&buf[next_out], oldi - next_out);
2610                 if (++i > next_out)
2611                   next_out = i;
2612                 continue;
2613             }
2614
2615             /* OK, this at least will *usually* work */
2616             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2617                 loggedOn = TRUE;
2618             }
2619
2620             if (loggedOn && !intfSet) {
2621                 if (ics_type == ICS_ICC) {
2622                   snprintf(str, MSG_SIZ,
2623                           "/set-quietly interface %s\n/set-quietly style 12\n",
2624                           programVersion);
2625                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2626                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2627                 } else if (ics_type == ICS_CHESSNET) {
2628                   snprintf(str, MSG_SIZ, "/style 12\n");
2629                 } else {
2630                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2631                   strcat(str, programVersion);
2632                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2633                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2634                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2635 #ifdef WIN32
2636                   strcat(str, "$iset nohighlight 1\n");
2637 #endif
2638                   strcat(str, "$iset lock 1\n$style 12\n");
2639                 }
2640                 SendToICS(str);
2641                 NotifyFrontendLogin();
2642                 intfSet = TRUE;
2643             }
2644
2645             if (started == STARTED_COMMENT) {
2646                 /* Accumulate characters in comment */
2647                 parse[parse_pos++] = buf[i];
2648                 if (buf[i] == '\n') {
2649                     parse[parse_pos] = NULLCHAR;
2650                     if(chattingPartner>=0) {
2651                         char mess[MSG_SIZ];
2652                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2653                         OutputChatMessage(chattingPartner, mess);
2654                         chattingPartner = -1;
2655                         next_out = i+1; // [HGM] suppress printing in ICS window
2656                     } else
2657                     if(!suppressKibitz) // [HGM] kibitz
2658                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2659                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2660                         int nrDigit = 0, nrAlph = 0, j;
2661                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2662                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2663                         parse[parse_pos] = NULLCHAR;
2664                         // try to be smart: if it does not look like search info, it should go to
2665                         // ICS interaction window after all, not to engine-output window.
2666                         for(j=0; j<parse_pos; j++) { // count letters and digits
2667                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2668                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2669                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2670                         }
2671                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2672                             int depth=0; float score;
2673                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2674                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2675                                 pvInfoList[forwardMostMove-1].depth = depth;
2676                                 pvInfoList[forwardMostMove-1].score = 100*score;
2677                             }
2678                             OutputKibitz(suppressKibitz, parse);
2679                         } else {
2680                             char tmp[MSG_SIZ];
2681                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2682                             SendToPlayer(tmp, strlen(tmp));
2683                         }
2684                         next_out = i+1; // [HGM] suppress printing in ICS window
2685                     }
2686                     started = STARTED_NONE;
2687                 } else {
2688                     /* Don't match patterns against characters in comment */
2689                     i++;
2690                     continue;
2691                 }
2692             }
2693             if (started == STARTED_CHATTER) {
2694                 if (buf[i] != '\n') {
2695                     /* Don't match patterns against characters in chatter */
2696                     i++;
2697                     continue;
2698                 }
2699                 started = STARTED_NONE;
2700                 if(suppressKibitz) next_out = i+1;
2701             }
2702
2703             /* Kludge to deal with rcmd protocol */
2704             if (firstTime && looking_at(buf, &i, "\001*")) {
2705                 DisplayFatalError(&buf[1], 0, 1);
2706                 continue;
2707             } else {
2708                 firstTime = FALSE;
2709             }
2710
2711             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2712                 ics_type = ICS_ICC;
2713                 ics_prefix = "/";
2714                 if (appData.debugMode)
2715                   fprintf(debugFP, "ics_type %d\n", ics_type);
2716                 continue;
2717             }
2718             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2719                 ics_type = ICS_FICS;
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, "chess.net")) {
2726                 ics_type = ICS_CHESSNET;
2727                 ics_prefix = "/";
2728                 if (appData.debugMode)
2729                   fprintf(debugFP, "ics_type %d\n", ics_type);
2730                 continue;
2731             }
2732
2733             if (!loggedOn &&
2734                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2735                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2736                  looking_at(buf, &i, "will be \"*\""))) {
2737               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2738               continue;
2739             }
2740
2741             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2742               char buf[MSG_SIZ];
2743               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2744               DisplayIcsInteractionTitle(buf);
2745               have_set_title = TRUE;
2746             }
2747
2748             /* skip finger notes */
2749             if (started == STARTED_NONE &&
2750                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2751                  (buf[i] == '1' && buf[i+1] == '0')) &&
2752                 buf[i+2] == ':' && buf[i+3] == ' ') {
2753               started = STARTED_CHATTER;
2754               i += 3;
2755               continue;
2756             }
2757
2758             oldi = i;
2759             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2760             if(appData.seekGraph) {
2761                 if(soughtPending && MatchSoughtLine(buf+i)) {
2762                     i = strstr(buf+i, "rated") - buf;
2763                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2764                     next_out = leftover_start = i;
2765                     started = STARTED_CHATTER;
2766                     suppressKibitz = TRUE;
2767                     continue;
2768                 }
2769                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2770                         && looking_at(buf, &i, "* ads displayed")) {
2771                     soughtPending = FALSE;
2772                     seekGraphUp = TRUE;
2773                     DrawSeekGraph();
2774                     continue;
2775                 }
2776                 if(appData.autoRefresh) {
2777                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2778                         int s = (ics_type == ICS_ICC); // ICC format differs
2779                         if(seekGraphUp)
2780                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2781                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2782                         looking_at(buf, &i, "*% "); // eat prompt
2783                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2784                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2785                         next_out = i; // suppress
2786                         continue;
2787                     }
2788                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2789                         char *p = star_match[0];
2790                         while(*p) {
2791                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2792                             while(*p && *p++ != ' '); // next
2793                         }
2794                         looking_at(buf, &i, "*% "); // eat prompt
2795                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2796                         next_out = i;
2797                         continue;
2798                     }
2799                 }
2800             }
2801
2802             /* skip formula vars */
2803             if (started == STARTED_NONE &&
2804                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2805               started = STARTED_CHATTER;
2806               i += 3;
2807               continue;
2808             }
2809
2810             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2811             if (appData.autoKibitz && started == STARTED_NONE &&
2812                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2813                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2814                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2815                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2816                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2817                         suppressKibitz = TRUE;
2818                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2819                         next_out = i;
2820                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2821                                 && (gameMode == IcsPlayingWhite)) ||
2822                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2823                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2824                             started = STARTED_CHATTER; // own kibitz we simply discard
2825                         else {
2826                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2827                             parse_pos = 0; parse[0] = NULLCHAR;
2828                             savingComment = TRUE;
2829                             suppressKibitz = gameMode != IcsObserving ? 2 :
2830                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2831                         }
2832                         continue;
2833                 } else
2834                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2835                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2836                          && atoi(star_match[0])) {
2837                     // suppress the acknowledgements of our own autoKibitz
2838                     char *p;
2839                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2840                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2841                     SendToPlayer(star_match[0], strlen(star_match[0]));
2842                     if(looking_at(buf, &i, "*% ")) // eat prompt
2843                         suppressKibitz = FALSE;
2844                     next_out = i;
2845                     continue;
2846                 }
2847             } // [HGM] kibitz: end of patch
2848
2849             // [HGM] chat: intercept tells by users for which we have an open chat window
2850             channel = -1;
2851             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2852                                            looking_at(buf, &i, "* whispers:") ||
2853                                            looking_at(buf, &i, "* kibitzes:") ||
2854                                            looking_at(buf, &i, "* shouts:") ||
2855                                            looking_at(buf, &i, "* c-shouts:") ||
2856                                            looking_at(buf, &i, "--> * ") ||
2857                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2858                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2859                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2860                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2861                 int p;
2862                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2863                 chattingPartner = -1;
2864
2865                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2866                 for(p=0; p<MAX_CHAT; p++) {
2867                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2868                     talker[0] = '['; strcat(talker, "] ");
2869                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2870                     chattingPartner = p; break;
2871                     }
2872                 } else
2873                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2874                 for(p=0; p<MAX_CHAT; p++) {
2875                     if(!strcmp("kibitzes", chatPartner[p])) {
2876                         talker[0] = '['; strcat(talker, "] ");
2877                         chattingPartner = p; break;
2878                     }
2879                 } else
2880                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2881                 for(p=0; p<MAX_CHAT; p++) {
2882                     if(!strcmp("whispers", chatPartner[p])) {
2883                         talker[0] = '['; strcat(talker, "] ");
2884                         chattingPartner = p; break;
2885                     }
2886                 } else
2887                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2888                   if(buf[i-8] == '-' && buf[i-3] == 't')
2889                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2890                     if(!strcmp("c-shouts", chatPartner[p])) {
2891                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2892                         chattingPartner = p; break;
2893                     }
2894                   }
2895                   if(chattingPartner < 0)
2896                   for(p=0; p<MAX_CHAT; p++) {
2897                     if(!strcmp("shouts", chatPartner[p])) {
2898                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2899                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2900                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2901                         chattingPartner = p; break;
2902                     }
2903                   }
2904                 }
2905                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2906                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2907                     talker[0] = 0; Colorize(ColorTell, FALSE);
2908                     chattingPartner = p; break;
2909                 }
2910                 if(chattingPartner<0) i = oldi; else {
2911                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2912                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2913                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2914                     started = STARTED_COMMENT;
2915                     parse_pos = 0; parse[0] = NULLCHAR;
2916                     savingComment = 3 + chattingPartner; // counts as TRUE
2917                     suppressKibitz = TRUE;
2918                     continue;
2919                 }
2920             } // [HGM] chat: end of patch
2921
2922             if (appData.zippyTalk || appData.zippyPlay) {
2923                 /* [DM] Backup address for color zippy lines */
2924                 backup = i;
2925 #if ZIPPY
2926                if (loggedOn == TRUE)
2927                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2928                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2929 #endif
2930             } // [DM] 'else { ' deleted
2931                 if (
2932                     /* Regular tells and says */
2933                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2934                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2935                     looking_at(buf, &i, "* says: ") ||
2936                     /* Don't color "message" or "messages" output */
2937                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2938                     looking_at(buf, &i, "*. * at *:*: ") ||
2939                     looking_at(buf, &i, "--* (*:*): ") ||
2940                     /* Message notifications (same color as tells) */
2941                     looking_at(buf, &i, "* has left a message ") ||
2942                     looking_at(buf, &i, "* just sent you a message:\n") ||
2943                     /* Whispers and kibitzes */
2944                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2945                     looking_at(buf, &i, "* kibitzes: ") ||
2946                     /* Channel tells */
2947                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2948
2949                   if (tkind == 1 && strchr(star_match[0], ':')) {
2950                       /* Avoid "tells you:" spoofs in channels */
2951                      tkind = 3;
2952                   }
2953                   if (star_match[0][0] == NULLCHAR ||
2954                       strchr(star_match[0], ' ') ||
2955                       (tkind == 3 && strchr(star_match[1], ' '))) {
2956                     /* Reject bogus matches */
2957                     i = oldi;
2958                   } else {
2959                     if (appData.colorize) {
2960                       if (oldi > next_out) {
2961                         SendToPlayer(&buf[next_out], oldi - next_out);
2962                         next_out = oldi;
2963                       }
2964                       switch (tkind) {
2965                       case 1:
2966                         Colorize(ColorTell, FALSE);
2967                         curColor = ColorTell;
2968                         break;
2969                       case 2:
2970                         Colorize(ColorKibitz, FALSE);
2971                         curColor = ColorKibitz;
2972                         break;
2973                       case 3:
2974                         p = strrchr(star_match[1], '(');
2975                         if (p == NULL) {
2976                           p = star_match[1];
2977                         } else {
2978                           p++;
2979                         }
2980                         if (atoi(p) == 1) {
2981                           Colorize(ColorChannel1, FALSE);
2982                           curColor = ColorChannel1;
2983                         } else {
2984                           Colorize(ColorChannel, FALSE);
2985                           curColor = ColorChannel;
2986                         }
2987                         break;
2988                       case 5:
2989                         curColor = ColorNormal;
2990                         break;
2991                       }
2992                     }
2993                     if (started == STARTED_NONE && appData.autoComment &&
2994                         (gameMode == IcsObserving ||
2995                          gameMode == IcsPlayingWhite ||
2996                          gameMode == IcsPlayingBlack)) {
2997                       parse_pos = i - oldi;
2998                       memcpy(parse, &buf[oldi], parse_pos);
2999                       parse[parse_pos] = NULLCHAR;
3000                       started = STARTED_COMMENT;
3001                       savingComment = TRUE;
3002                     } else {
3003                       started = STARTED_CHATTER;
3004                       savingComment = FALSE;
3005                     }
3006                     loggedOn = TRUE;
3007                     continue;
3008                   }
3009                 }
3010
3011                 if (looking_at(buf, &i, "* s-shouts: ") ||
3012                     looking_at(buf, &i, "* c-shouts: ")) {
3013                     if (appData.colorize) {
3014                         if (oldi > next_out) {
3015                             SendToPlayer(&buf[next_out], oldi - next_out);
3016                             next_out = oldi;
3017                         }
3018                         Colorize(ColorSShout, FALSE);
3019                         curColor = ColorSShout;
3020                     }
3021                     loggedOn = TRUE;
3022                     started = STARTED_CHATTER;
3023                     continue;
3024                 }
3025
3026                 if (looking_at(buf, &i, "--->")) {
3027                     loggedOn = TRUE;
3028                     continue;
3029                 }
3030
3031                 if (looking_at(buf, &i, "* shouts: ") ||
3032                     looking_at(buf, &i, "--> ")) {
3033                     if (appData.colorize) {
3034                         if (oldi > next_out) {
3035                             SendToPlayer(&buf[next_out], oldi - next_out);
3036                             next_out = oldi;
3037                         }
3038                         Colorize(ColorShout, FALSE);
3039                         curColor = ColorShout;
3040                     }
3041                     loggedOn = TRUE;
3042                     started = STARTED_CHATTER;
3043                     continue;
3044                 }
3045
3046                 if (looking_at( buf, &i, "Challenge:")) {
3047                     if (appData.colorize) {
3048                         if (oldi > next_out) {
3049                             SendToPlayer(&buf[next_out], oldi - next_out);
3050                             next_out = oldi;
3051                         }
3052                         Colorize(ColorChallenge, FALSE);
3053                         curColor = ColorChallenge;
3054                     }
3055                     loggedOn = TRUE;
3056                     continue;
3057                 }
3058
3059                 if (looking_at(buf, &i, "* offers you") ||
3060                     looking_at(buf, &i, "* offers to be") ||
3061                     looking_at(buf, &i, "* would like to") ||
3062                     looking_at(buf, &i, "* requests to") ||
3063                     looking_at(buf, &i, "Your opponent offers") ||
3064                     looking_at(buf, &i, "Your opponent requests")) {
3065
3066                     if (appData.colorize) {
3067                         if (oldi > next_out) {
3068                             SendToPlayer(&buf[next_out], oldi - next_out);
3069                             next_out = oldi;
3070                         }
3071                         Colorize(ColorRequest, FALSE);
3072                         curColor = ColorRequest;
3073                     }
3074                     continue;
3075                 }
3076
3077                 if (looking_at(buf, &i, "* (*) seeking")) {
3078                     if (appData.colorize) {
3079                         if (oldi > next_out) {
3080                             SendToPlayer(&buf[next_out], oldi - next_out);
3081                             next_out = oldi;
3082                         }
3083                         Colorize(ColorSeek, FALSE);
3084                         curColor = ColorSeek;
3085                     }
3086                     continue;
3087             }
3088
3089             if (looking_at(buf, &i, "\\   ")) {
3090                 if (prevColor != ColorNormal) {
3091                     if (oldi > next_out) {
3092                         SendToPlayer(&buf[next_out], oldi - next_out);
3093                         next_out = oldi;
3094                     }
3095                     Colorize(prevColor, TRUE);
3096                     curColor = prevColor;
3097                 }
3098                 if (savingComment) {
3099                     parse_pos = i - oldi;
3100                     memcpy(parse, &buf[oldi], parse_pos);
3101                     parse[parse_pos] = NULLCHAR;
3102                     started = STARTED_COMMENT;
3103                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3104                         chattingPartner = savingComment - 3; // kludge to remember the box
3105                 } else {
3106                     started = STARTED_CHATTER;
3107                 }
3108                 continue;
3109             }
3110
3111             if (looking_at(buf, &i, "Black Strength :") ||
3112                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3113                 looking_at(buf, &i, "<10>") ||
3114                 looking_at(buf, &i, "#@#")) {
3115                 /* Wrong board style */
3116                 loggedOn = TRUE;
3117                 SendToICS(ics_prefix);
3118                 SendToICS("set style 12\n");
3119                 SendToICS(ics_prefix);
3120                 SendToICS("refresh\n");
3121                 continue;
3122             }
3123
3124             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3125                 ICSInitScript();
3126                 have_sent_ICS_logon = 1;
3127                 continue;
3128             }
3129
3130             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3131                 (looking_at(buf, &i, "\n<12> ") ||
3132                  looking_at(buf, &i, "<12> "))) {
3133                 loggedOn = TRUE;
3134                 if (oldi > next_out) {
3135                     SendToPlayer(&buf[next_out], oldi - next_out);
3136                 }
3137                 next_out = i;
3138                 started = STARTED_BOARD;
3139                 parse_pos = 0;
3140                 continue;
3141             }
3142
3143             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3144                 looking_at(buf, &i, "<b1> ")) {
3145                 if (oldi > next_out) {
3146                     SendToPlayer(&buf[next_out], oldi - next_out);
3147                 }
3148                 next_out = i;
3149                 started = STARTED_HOLDINGS;
3150                 parse_pos = 0;
3151                 continue;
3152             }
3153
3154             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3155                 loggedOn = TRUE;
3156                 /* Header for a move list -- first line */
3157
3158                 switch (ics_getting_history) {
3159                   case H_FALSE:
3160                     switch (gameMode) {
3161                       case IcsIdle:
3162                       case BeginningOfGame:
3163                         /* User typed "moves" or "oldmoves" while we
3164                            were idle.  Pretend we asked for these
3165                            moves and soak them up so user can step
3166                            through them and/or save them.
3167                            */
3168                         Reset(FALSE, TRUE);
3169                         gameMode = IcsObserving;
3170                         ModeHighlight();
3171                         ics_gamenum = -1;
3172                         ics_getting_history = H_GOT_UNREQ_HEADER;
3173                         break;
3174                       case EditGame: /*?*/
3175                       case EditPosition: /*?*/
3176                         /* Should above feature work in these modes too? */
3177                         /* For now it doesn't */
3178                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3179                         break;
3180                       default:
3181                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3182                         break;
3183                     }
3184                     break;
3185                   case H_REQUESTED:
3186                     /* Is this the right one? */
3187                     if (gameInfo.white && gameInfo.black &&
3188                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3189                         strcmp(gameInfo.black, star_match[2]) == 0) {
3190                         /* All is well */
3191                         ics_getting_history = H_GOT_REQ_HEADER;
3192                     }
3193                     break;
3194                   case H_GOT_REQ_HEADER:
3195                   case H_GOT_UNREQ_HEADER:
3196                   case H_GOT_UNWANTED_HEADER:
3197                   case H_GETTING_MOVES:
3198                     /* Should not happen */
3199                     DisplayError(_("Error gathering move list: two headers"), 0);
3200                     ics_getting_history = H_FALSE;
3201                     break;
3202                 }
3203
3204                 /* Save player ratings into gameInfo if needed */
3205                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3206                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3207                     (gameInfo.whiteRating == -1 ||
3208                      gameInfo.blackRating == -1)) {
3209
3210                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3211                     gameInfo.blackRating = string_to_rating(star_match[3]);
3212                     if (appData.debugMode)
3213                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3214                               gameInfo.whiteRating, gameInfo.blackRating);
3215                 }
3216                 continue;
3217             }
3218
3219             if (looking_at(buf, &i,
3220               "* * match, initial time: * minute*, increment: * second")) {
3221                 /* Header for a move list -- second line */
3222                 /* Initial board will follow if this is a wild game */
3223                 if (gameInfo.event != NULL) free(gameInfo.event);
3224                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3225                 gameInfo.event = StrSave(str);
3226                 /* [HGM] we switched variant. Translate boards if needed. */
3227                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3228                 continue;
3229             }
3230
3231             if (looking_at(buf, &i, "Move  ")) {
3232                 /* Beginning of a move list */
3233                 switch (ics_getting_history) {
3234                   case H_FALSE:
3235                     /* Normally should not happen */
3236                     /* Maybe user hit reset while we were parsing */
3237                     break;
3238                   case H_REQUESTED:
3239                     /* Happens if we are ignoring a move list that is not
3240                      * the one we just requested.  Common if the user
3241                      * tries to observe two games without turning off
3242                      * getMoveList */
3243                     break;
3244                   case H_GETTING_MOVES:
3245                     /* Should not happen */
3246                     DisplayError(_("Error gathering move list: nested"), 0);
3247                     ics_getting_history = H_FALSE;
3248                     break;
3249                   case H_GOT_REQ_HEADER:
3250                     ics_getting_history = H_GETTING_MOVES;
3251                     started = STARTED_MOVES;
3252                     parse_pos = 0;
3253                     if (oldi > next_out) {
3254                         SendToPlayer(&buf[next_out], oldi - next_out);
3255                     }
3256                     break;
3257                   case H_GOT_UNREQ_HEADER:
3258                     ics_getting_history = H_GETTING_MOVES;
3259                     started = STARTED_MOVES_NOHIDE;
3260                     parse_pos = 0;
3261                     break;
3262                   case H_GOT_UNWANTED_HEADER:
3263                     ics_getting_history = H_FALSE;
3264                     break;
3265                 }
3266                 continue;
3267             }
3268
3269             if (looking_at(buf, &i, "% ") ||
3270                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3271                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3272                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3273                     soughtPending = FALSE;
3274                     seekGraphUp = TRUE;
3275                     DrawSeekGraph();
3276                 }
3277                 if(suppressKibitz) next_out = i;
3278                 savingComment = FALSE;
3279                 suppressKibitz = 0;
3280                 switch (started) {
3281                   case STARTED_MOVES:
3282                   case STARTED_MOVES_NOHIDE:
3283                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3284                     parse[parse_pos + i - oldi] = NULLCHAR;
3285                     ParseGameHistory(parse);
3286 #if ZIPPY
3287                     if (appData.zippyPlay && first.initDone) {
3288                         FeedMovesToProgram(&first, forwardMostMove);
3289                         if (gameMode == IcsPlayingWhite) {
3290                             if (WhiteOnMove(forwardMostMove)) {
3291                                 if (first.sendTime) {
3292                                   if (first.useColors) {
3293                                     SendToProgram("black\n", &first);
3294                                   }
3295                                   SendTimeRemaining(&first, TRUE);
3296                                 }
3297                                 if (first.useColors) {
3298                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3299                                 }
3300                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3301                                 first.maybeThinking = TRUE;
3302                             } else {
3303                                 if (first.usePlayother) {
3304                                   if (first.sendTime) {
3305                                     SendTimeRemaining(&first, TRUE);
3306                                   }
3307                                   SendToProgram("playother\n", &first);
3308                                   firstMove = FALSE;
3309                                 } else {
3310                                   firstMove = TRUE;
3311                                 }
3312                             }
3313                         } else if (gameMode == IcsPlayingBlack) {
3314                             if (!WhiteOnMove(forwardMostMove)) {
3315                                 if (first.sendTime) {
3316                                   if (first.useColors) {
3317                                     SendToProgram("white\n", &first);
3318                                   }
3319                                   SendTimeRemaining(&first, FALSE);
3320                                 }
3321                                 if (first.useColors) {
3322                                   SendToProgram("black\n", &first);
3323                                 }
3324                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3325                                 first.maybeThinking = TRUE;
3326                             } else {
3327                                 if (first.usePlayother) {
3328                                   if (first.sendTime) {
3329                                     SendTimeRemaining(&first, FALSE);
3330                                   }
3331                                   SendToProgram("playother\n", &first);
3332                                   firstMove = FALSE;
3333                                 } else {
3334                                   firstMove = TRUE;
3335                                 }
3336                             }
3337                         }
3338                     }
3339 #endif
3340                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3341                         /* Moves came from oldmoves or moves command
3342                            while we weren't doing anything else.
3343                            */
3344                         currentMove = forwardMostMove;
3345                         ClearHighlights();/*!!could figure this out*/
3346                         flipView = appData.flipView;
3347                         DrawPosition(TRUE, boards[currentMove]);
3348                         DisplayBothClocks();
3349                         snprintf(str, MSG_SIZ, "%s vs. %s",
3350                                 gameInfo.white, gameInfo.black);
3351                         DisplayTitle(str);
3352                         gameMode = IcsIdle;
3353                     } else {
3354                         /* Moves were history of an active game */
3355                         if (gameInfo.resultDetails != NULL) {
3356                             free(gameInfo.resultDetails);
3357                             gameInfo.resultDetails = NULL;
3358                         }
3359                     }
3360                     HistorySet(parseList, backwardMostMove,
3361                                forwardMostMove, currentMove-1);
3362                     DisplayMove(currentMove - 1);
3363                     if (started == STARTED_MOVES) next_out = i;
3364                     started = STARTED_NONE;
3365                     ics_getting_history = H_FALSE;
3366                     break;
3367
3368                   case STARTED_OBSERVE:
3369                     started = STARTED_NONE;
3370                     SendToICS(ics_prefix);
3371                     SendToICS("refresh\n");
3372                     break;
3373
3374                   default:
3375                     break;
3376                 }
3377                 if(bookHit) { // [HGM] book: simulate book reply
3378                     static char bookMove[MSG_SIZ]; // a bit generous?
3379
3380                     programStats.nodes = programStats.depth = programStats.time =
3381                     programStats.score = programStats.got_only_move = 0;
3382                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3383
3384                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3385                     strcat(bookMove, bookHit);
3386                     HandleMachineMove(bookMove, &first);
3387                 }
3388                 continue;
3389             }
3390
3391             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3392                  started == STARTED_HOLDINGS ||
3393                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3394                 /* Accumulate characters in move list or board */
3395                 parse[parse_pos++] = buf[i];
3396             }
3397
3398             /* Start of game messages.  Mostly we detect start of game
3399                when the first board image arrives.  On some versions
3400                of the ICS, though, we need to do a "refresh" after starting
3401                to observe in order to get the current board right away. */
3402             if (looking_at(buf, &i, "Adding game * to observation list")) {
3403                 started = STARTED_OBSERVE;
3404                 continue;
3405             }
3406
3407             /* Handle auto-observe */
3408             if (appData.autoObserve &&
3409                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3410                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3411                 char *player;
3412                 /* Choose the player that was highlighted, if any. */
3413                 if (star_match[0][0] == '\033' ||
3414                     star_match[1][0] != '\033') {
3415                     player = star_match[0];
3416                 } else {
3417                     player = star_match[2];
3418                 }
3419                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3420                         ics_prefix, StripHighlightAndTitle(player));
3421                 SendToICS(str);
3422
3423                 /* Save ratings from notify string */
3424                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3425                 player1Rating = string_to_rating(star_match[1]);
3426                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3427                 player2Rating = string_to_rating(star_match[3]);
3428
3429                 if (appData.debugMode)
3430                   fprintf(debugFP,
3431                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3432                           player1Name, player1Rating,
3433                           player2Name, player2Rating);
3434
3435                 continue;
3436             }
3437
3438             /* Deal with automatic examine mode after a game,
3439                and with IcsObserving -> IcsExamining transition */
3440             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3441                 looking_at(buf, &i, "has made you an examiner of game *")) {
3442
3443                 int gamenum = atoi(star_match[0]);
3444                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3445                     gamenum == ics_gamenum) {
3446                     /* We were already playing or observing this game;
3447                        no need to refetch history */
3448                     gameMode = IcsExamining;
3449                     if (pausing) {
3450                         pauseExamForwardMostMove = forwardMostMove;
3451                     } else if (currentMove < forwardMostMove) {
3452                         ForwardInner(forwardMostMove);
3453                     }
3454                 } else {
3455                     /* I don't think this case really can happen */
3456                     SendToICS(ics_prefix);
3457                     SendToICS("refresh\n");
3458                 }
3459                 continue;
3460             }
3461
3462             /* Error messages */
3463 //          if (ics_user_moved) {
3464             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3465                 if (looking_at(buf, &i, "Illegal move") ||
3466                     looking_at(buf, &i, "Not a legal move") ||
3467                     looking_at(buf, &i, "Your king is in check") ||
3468                     looking_at(buf, &i, "It isn't your turn") ||
3469                     looking_at(buf, &i, "It is not your move")) {
3470                     /* Illegal move */
3471                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3472                         currentMove = forwardMostMove-1;
3473                         DisplayMove(currentMove - 1); /* before DMError */
3474                         DrawPosition(FALSE, boards[currentMove]);
3475                         SwitchClocks(forwardMostMove-1); // [HGM] race
3476                         DisplayBothClocks();
3477                     }
3478                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3479                     ics_user_moved = 0;
3480                     continue;
3481                 }
3482             }
3483
3484             if (looking_at(buf, &i, "still have time") ||
3485                 looking_at(buf, &i, "not out of time") ||
3486                 looking_at(buf, &i, "either player is out of time") ||
3487                 looking_at(buf, &i, "has timeseal; checking")) {
3488                 /* We must have called his flag a little too soon */
3489                 whiteFlag = blackFlag = FALSE;
3490                 continue;
3491             }
3492
3493             if (looking_at(buf, &i, "added * seconds to") ||
3494                 looking_at(buf, &i, "seconds were added to")) {
3495                 /* Update the clocks */
3496                 SendToICS(ics_prefix);
3497                 SendToICS("refresh\n");
3498                 continue;
3499             }
3500
3501             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3502                 ics_clock_paused = TRUE;
3503                 StopClocks();
3504                 continue;
3505             }
3506
3507             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3508                 ics_clock_paused = FALSE;
3509                 StartClocks();
3510                 continue;
3511             }
3512
3513             /* Grab player ratings from the Creating: message.
3514                Note we have to check for the special case when
3515                the ICS inserts things like [white] or [black]. */
3516             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3517                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3518                 /* star_matches:
3519                    0    player 1 name (not necessarily white)
3520                    1    player 1 rating
3521                    2    empty, white, or black (IGNORED)
3522                    3    player 2 name (not necessarily black)
3523                    4    player 2 rating
3524
3525                    The names/ratings are sorted out when the game
3526                    actually starts (below).
3527                 */
3528                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3529                 player1Rating = string_to_rating(star_match[1]);
3530                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3531                 player2Rating = string_to_rating(star_match[4]);
3532
3533                 if (appData.debugMode)
3534                   fprintf(debugFP,
3535                           "Ratings from 'Creating:' %s %d, %s %d\n",
3536                           player1Name, player1Rating,
3537                           player2Name, player2Rating);
3538
3539                 continue;
3540             }
3541
3542             /* Improved generic start/end-of-game messages */
3543             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3544                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3545                 /* If tkind == 0: */
3546                 /* star_match[0] is the game number */
3547                 /*           [1] is the white player's name */
3548                 /*           [2] is the black player's name */
3549                 /* For end-of-game: */
3550                 /*           [3] is the reason for the game end */
3551                 /*           [4] is a PGN end game-token, preceded by " " */
3552                 /* For start-of-game: */
3553                 /*           [3] begins with "Creating" or "Continuing" */
3554                 /*           [4] is " *" or empty (don't care). */
3555                 int gamenum = atoi(star_match[0]);
3556                 char *whitename, *blackname, *why, *endtoken;
3557                 ChessMove endtype = EndOfFile;
3558
3559                 if (tkind == 0) {
3560                   whitename = star_match[1];
3561                   blackname = star_match[2];
3562                   why = star_match[3];
3563                   endtoken = star_match[4];
3564                 } else {
3565                   whitename = star_match[1];
3566                   blackname = star_match[3];
3567                   why = star_match[5];
3568                   endtoken = star_match[6];
3569                 }
3570
3571                 /* Game start messages */
3572                 if (strncmp(why, "Creating ", 9) == 0 ||
3573                     strncmp(why, "Continuing ", 11) == 0) {
3574                     gs_gamenum = gamenum;
3575                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3576                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3577 #if ZIPPY
3578                     if (appData.zippyPlay) {
3579                         ZippyGameStart(whitename, blackname);
3580                     }
3581 #endif /*ZIPPY*/
3582                     partnerBoardValid = FALSE; // [HGM] bughouse
3583                     continue;
3584                 }
3585
3586                 /* Game end messages */
3587                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3588                     ics_gamenum != gamenum) {
3589                     continue;
3590                 }
3591                 while (endtoken[0] == ' ') endtoken++;
3592                 switch (endtoken[0]) {
3593                   case '*':
3594                   default:
3595                     endtype = GameUnfinished;
3596                     break;
3597                   case '0':
3598                     endtype = BlackWins;
3599                     break;
3600                   case '1':
3601                     if (endtoken[1] == '/')
3602                       endtype = GameIsDrawn;
3603                     else
3604                       endtype = WhiteWins;
3605                     break;
3606                 }
3607                 GameEnds(endtype, why, GE_ICS);
3608 #if ZIPPY
3609                 if (appData.zippyPlay && first.initDone) {
3610                     ZippyGameEnd(endtype, why);
3611                     if (first.pr == NULL) {
3612                       /* Start the next process early so that we'll
3613                          be ready for the next challenge */
3614                       StartChessProgram(&first);
3615                     }
3616                     /* Send "new" early, in case this command takes
3617                        a long time to finish, so that we'll be ready
3618                        for the next challenge. */
3619                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3620                     Reset(TRUE, TRUE);
3621                 }
3622 #endif /*ZIPPY*/
3623                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3624                 continue;
3625             }
3626
3627             if (looking_at(buf, &i, "Removing game * from observation") ||
3628                 looking_at(buf, &i, "no longer observing game *") ||
3629                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3630                 if (gameMode == IcsObserving &&
3631                     atoi(star_match[0]) == ics_gamenum)
3632                   {
3633                       /* icsEngineAnalyze */
3634                       if (appData.icsEngineAnalyze) {
3635                             ExitAnalyzeMode();
3636                             ModeHighlight();
3637                       }
3638                       StopClocks();
3639                       gameMode = IcsIdle;
3640                       ics_gamenum = -1;
3641                       ics_user_moved = FALSE;
3642                   }
3643                 continue;
3644             }
3645
3646             if (looking_at(buf, &i, "no longer examining game *")) {
3647                 if (gameMode == IcsExamining &&
3648                     atoi(star_match[0]) == ics_gamenum)
3649                   {
3650                       gameMode = IcsIdle;
3651                       ics_gamenum = -1;
3652                       ics_user_moved = FALSE;
3653                   }
3654                 continue;
3655             }
3656
3657             /* Advance leftover_start past any newlines we find,
3658                so only partial lines can get reparsed */
3659             if (looking_at(buf, &i, "\n")) {
3660                 prevColor = curColor;
3661                 if (curColor != ColorNormal) {
3662                     if (oldi > next_out) {
3663                         SendToPlayer(&buf[next_out], oldi - next_out);
3664                         next_out = oldi;
3665                     }
3666                     Colorize(ColorNormal, FALSE);
3667                     curColor = ColorNormal;
3668                 }
3669                 if (started == STARTED_BOARD) {
3670                     started = STARTED_NONE;
3671                     parse[parse_pos] = NULLCHAR;
3672                     ParseBoard12(parse);
3673                     ics_user_moved = 0;
3674
3675                     /* Send premove here */
3676                     if (appData.premove) {
3677                       char str[MSG_SIZ];
3678                       if (currentMove == 0 &&
3679                           gameMode == IcsPlayingWhite &&
3680                           appData.premoveWhite) {
3681                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3682                         if (appData.debugMode)
3683                           fprintf(debugFP, "Sending premove:\n");
3684                         SendToICS(str);
3685                       } else if (currentMove == 1 &&
3686                                  gameMode == IcsPlayingBlack &&
3687                                  appData.premoveBlack) {
3688                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3689                         if (appData.debugMode)
3690                           fprintf(debugFP, "Sending premove:\n");
3691                         SendToICS(str);
3692                       } else if (gotPremove) {
3693                         gotPremove = 0;
3694                         ClearPremoveHighlights();
3695                         if (appData.debugMode)
3696                           fprintf(debugFP, "Sending premove:\n");
3697                           UserMoveEvent(premoveFromX, premoveFromY,
3698                                         premoveToX, premoveToY,
3699                                         premovePromoChar);
3700                       }
3701                     }
3702
3703                     /* Usually suppress following prompt */
3704                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3705                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3706                         if (looking_at(buf, &i, "*% ")) {
3707                             savingComment = FALSE;
3708                             suppressKibitz = 0;
3709                         }
3710                     }
3711                     next_out = i;
3712                 } else if (started == STARTED_HOLDINGS) {
3713                     int gamenum;
3714                     char new_piece[MSG_SIZ];
3715                     started = STARTED_NONE;
3716                     parse[parse_pos] = NULLCHAR;
3717                     if (appData.debugMode)
3718                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3719                                                         parse, currentMove);
3720                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3721                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3722                         if (gameInfo.variant == VariantNormal) {
3723                           /* [HGM] We seem to switch variant during a game!
3724                            * Presumably no holdings were displayed, so we have
3725                            * to move the position two files to the right to
3726                            * create room for them!
3727                            */
3728                           VariantClass newVariant;
3729                           switch(gameInfo.boardWidth) { // base guess on board width
3730                                 case 9:  newVariant = VariantShogi; break;
3731                                 case 10: newVariant = VariantGreat; break;
3732                                 default: newVariant = VariantCrazyhouse; break;
3733                           }
3734                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3735                           /* Get a move list just to see the header, which
3736                              will tell us whether this is really bug or zh */
3737                           if (ics_getting_history == H_FALSE) {
3738                             ics_getting_history = H_REQUESTED;
3739                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3740                             SendToICS(str);
3741                           }
3742                         }
3743                         new_piece[0] = NULLCHAR;
3744                         sscanf(parse, "game %d white [%s black [%s <- %s",
3745                                &gamenum, white_holding, black_holding,
3746                                new_piece);
3747                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3748                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3749                         /* [HGM] copy holdings to board holdings area */
3750                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3751                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3752                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3753 #if ZIPPY
3754                         if (appData.zippyPlay && first.initDone) {
3755                             ZippyHoldings(white_holding, black_holding,
3756                                           new_piece);
3757                         }
3758 #endif /*ZIPPY*/
3759                         if (tinyLayout || smallLayout) {
3760                             char wh[16], bh[16];
3761                             PackHolding(wh, white_holding);
3762                             PackHolding(bh, black_holding);
3763                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3764                                     gameInfo.white, gameInfo.black);
3765                         } else {
3766                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3767                                     gameInfo.white, white_holding,
3768                                     gameInfo.black, black_holding);
3769                         }
3770                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3771                         DrawPosition(FALSE, boards[currentMove]);
3772                         DisplayTitle(str);
3773                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3774                         sscanf(parse, "game %d white [%s black [%s <- %s",
3775                                &gamenum, white_holding, black_holding,
3776                                new_piece);
3777                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3778                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3779                         /* [HGM] copy holdings to partner-board holdings area */
3780                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3781                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3782                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3783                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3784                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3785                       }
3786                     }
3787                     /* Suppress following prompt */
3788                     if (looking_at(buf, &i, "*% ")) {
3789                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3790                         savingComment = FALSE;
3791                         suppressKibitz = 0;
3792                     }
3793                     next_out = i;
3794                 }
3795                 continue;
3796             }
3797
3798             i++;                /* skip unparsed character and loop back */
3799         }
3800
3801         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3802 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3803 //          SendToPlayer(&buf[next_out], i - next_out);
3804             started != STARTED_HOLDINGS && leftover_start > next_out) {
3805             SendToPlayer(&buf[next_out], leftover_start - next_out);
3806             next_out = i;
3807         }
3808
3809         leftover_len = buf_len - leftover_start;
3810         /* if buffer ends with something we couldn't parse,
3811            reparse it after appending the next read */
3812
3813     } else if (count == 0) {
3814         RemoveInputSource(isr);
3815         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3816     } else {
3817         DisplayFatalError(_("Error reading from ICS"), error, 1);
3818     }
3819 }
3820
3821
3822 /* Board style 12 looks like this:
3823
3824    <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
3825
3826  * The "<12> " is stripped before it gets to this routine.  The two
3827  * trailing 0's (flip state and clock ticking) are later addition, and
3828  * some chess servers may not have them, or may have only the first.
3829  * Additional trailing fields may be added in the future.
3830  */
3831
3832 #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"
3833
3834 #define RELATION_OBSERVING_PLAYED    0
3835 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3836 #define RELATION_PLAYING_MYMOVE      1
3837 #define RELATION_PLAYING_NOTMYMOVE  -1
3838 #define RELATION_EXAMINING           2
3839 #define RELATION_ISOLATED_BOARD     -3
3840 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3841
3842 void
3843 ParseBoard12(string)
3844      char *string;
3845 {
3846     GameMode newGameMode;
3847     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3848     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3849     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3850     char to_play, board_chars[200];
3851     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3852     char black[32], white[32];
3853     Board board;
3854     int prevMove = currentMove;
3855     int ticking = 2;
3856     ChessMove moveType;
3857     int fromX, fromY, toX, toY;
3858     char promoChar;
3859     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3860     char *bookHit = NULL; // [HGM] book
3861     Boolean weird = FALSE, reqFlag = FALSE;
3862
3863     fromX = fromY = toX = toY = -1;
3864
3865     newGame = FALSE;
3866
3867     if (appData.debugMode)
3868       fprintf(debugFP, _("Parsing board: %s\n"), string);
3869
3870     move_str[0] = NULLCHAR;
3871     elapsed_time[0] = NULLCHAR;
3872     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3873         int  i = 0, j;
3874         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3875             if(string[i] == ' ') { ranks++; files = 0; }
3876             else files++;
3877             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3878             i++;
3879         }
3880         for(j = 0; j <i; j++) board_chars[j] = string[j];
3881         board_chars[i] = '\0';
3882         string += i + 1;
3883     }
3884     n = sscanf(string, PATTERN, &to_play, &double_push,
3885                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3886                &gamenum, white, black, &relation, &basetime, &increment,
3887                &white_stren, &black_stren, &white_time, &black_time,
3888                &moveNum, str, elapsed_time, move_str, &ics_flip,
3889                &ticking);
3890
3891     if (n < 21) {
3892         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3893         DisplayError(str, 0);
3894         return;
3895     }
3896
3897     /* Convert the move number to internal form */
3898     moveNum = (moveNum - 1) * 2;
3899     if (to_play == 'B') moveNum++;
3900     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3901       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3902                         0, 1);
3903       return;
3904     }
3905
3906     switch (relation) {
3907       case RELATION_OBSERVING_PLAYED:
3908       case RELATION_OBSERVING_STATIC:
3909         if (gamenum == -1) {
3910             /* Old ICC buglet */
3911             relation = RELATION_OBSERVING_STATIC;
3912         }
3913         newGameMode = IcsObserving;
3914         break;
3915       case RELATION_PLAYING_MYMOVE:
3916       case RELATION_PLAYING_NOTMYMOVE:
3917         newGameMode =
3918           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3919             IcsPlayingWhite : IcsPlayingBlack;
3920         break;
3921       case RELATION_EXAMINING:
3922         newGameMode = IcsExamining;
3923         break;
3924       case RELATION_ISOLATED_BOARD:
3925       default:
3926         /* Just display this board.  If user was doing something else,
3927            we will forget about it until the next board comes. */
3928         newGameMode = IcsIdle;
3929         break;
3930       case RELATION_STARTING_POSITION:
3931         newGameMode = gameMode;
3932         break;
3933     }
3934
3935     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3936          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3937       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3938       char *toSqr;
3939       for (k = 0; k < ranks; k++) {
3940         for (j = 0; j < files; j++)
3941           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3942         if(gameInfo.holdingsWidth > 1) {
3943              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3944              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3945         }
3946       }
3947       CopyBoard(partnerBoard, board);
3948       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3949         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3950         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3951       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3952       if(toSqr = strchr(str, '-')) {
3953         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3954         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3955       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3956       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3957       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3958       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3959       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3960       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3961                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3962       DisplayMessage(partnerStatus, "");
3963         partnerBoardValid = TRUE;
3964       return;
3965     }
3966
3967     /* Modify behavior for initial board display on move listing
3968        of wild games.
3969        */
3970     switch (ics_getting_history) {
3971       case H_FALSE:
3972       case H_REQUESTED:
3973         break;
3974       case H_GOT_REQ_HEADER:
3975       case H_GOT_UNREQ_HEADER:
3976         /* This is the initial position of the current game */
3977         gamenum = ics_gamenum;
3978         moveNum = 0;            /* old ICS bug workaround */
3979         if (to_play == 'B') {
3980           startedFromSetupPosition = TRUE;
3981           blackPlaysFirst = TRUE;
3982           moveNum = 1;
3983           if (forwardMostMove == 0) forwardMostMove = 1;
3984           if (backwardMostMove == 0) backwardMostMove = 1;
3985           if (currentMove == 0) currentMove = 1;
3986         }
3987         newGameMode = gameMode;
3988         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3989         break;
3990       case H_GOT_UNWANTED_HEADER:
3991         /* This is an initial board that we don't want */
3992         return;
3993       case H_GETTING_MOVES:
3994         /* Should not happen */
3995         DisplayError(_("Error gathering move list: extra board"), 0);
3996         ics_getting_history = H_FALSE;
3997         return;
3998     }
3999
4000    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4001                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4002      /* [HGM] We seem to have switched variant unexpectedly
4003       * Try to guess new variant from board size
4004       */
4005           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4006           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4007           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4008           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4009           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4010           if(!weird) newVariant = VariantNormal;
4011           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4012           /* Get a move list just to see the header, which
4013              will tell us whether this is really bug or zh */
4014           if (ics_getting_history == H_FALSE) {
4015             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4016             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4017             SendToICS(str);
4018           }
4019     }
4020
4021     /* Take action if this is the first board of a new game, or of a
4022        different game than is currently being displayed.  */
4023     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4024         relation == RELATION_ISOLATED_BOARD) {
4025
4026         /* Forget the old game and get the history (if any) of the new one */
4027         if (gameMode != BeginningOfGame) {
4028           Reset(TRUE, TRUE);
4029         }
4030         newGame = TRUE;
4031         if (appData.autoRaiseBoard) BoardToTop();
4032         prevMove = -3;
4033         if (gamenum == -1) {
4034             newGameMode = IcsIdle;
4035         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4036                    appData.getMoveList && !reqFlag) {
4037             /* Need to get game history */
4038             ics_getting_history = H_REQUESTED;
4039             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4040             SendToICS(str);
4041         }
4042
4043         /* Initially flip the board to have black on the bottom if playing
4044            black or if the ICS flip flag is set, but let the user change
4045            it with the Flip View button. */
4046         flipView = appData.autoFlipView ?
4047           (newGameMode == IcsPlayingBlack) || ics_flip :
4048           appData.flipView;
4049
4050         /* Done with values from previous mode; copy in new ones */
4051         gameMode = newGameMode;
4052         ModeHighlight();
4053         ics_gamenum = gamenum;
4054         if (gamenum == gs_gamenum) {
4055             int klen = strlen(gs_kind);
4056             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4057             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4058             gameInfo.event = StrSave(str);
4059         } else {
4060             gameInfo.event = StrSave("ICS game");
4061         }
4062         gameInfo.site = StrSave(appData.icsHost);
4063         gameInfo.date = PGNDate();
4064         gameInfo.round = StrSave("-");
4065         gameInfo.white = StrSave(white);
4066         gameInfo.black = StrSave(black);
4067         timeControl = basetime * 60 * 1000;
4068         timeControl_2 = 0;
4069         timeIncrement = increment * 1000;
4070         movesPerSession = 0;
4071         gameInfo.timeControl = TimeControlTagValue();
4072         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4073   if (appData.debugMode) {
4074     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4075     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4076     setbuf(debugFP, NULL);
4077   }
4078
4079         gameInfo.outOfBook = NULL;
4080
4081         /* Do we have the ratings? */
4082         if (strcmp(player1Name, white) == 0 &&
4083             strcmp(player2Name, black) == 0) {
4084             if (appData.debugMode)
4085               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4086                       player1Rating, player2Rating);
4087             gameInfo.whiteRating = player1Rating;
4088             gameInfo.blackRating = player2Rating;
4089         } else if (strcmp(player2Name, white) == 0 &&
4090                    strcmp(player1Name, black) == 0) {
4091             if (appData.debugMode)
4092               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4093                       player2Rating, player1Rating);
4094             gameInfo.whiteRating = player2Rating;
4095             gameInfo.blackRating = player1Rating;
4096         }
4097         player1Name[0] = player2Name[0] = NULLCHAR;
4098
4099         /* Silence shouts if requested */
4100         if (appData.quietPlay &&
4101             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4102             SendToICS(ics_prefix);
4103             SendToICS("set shout 0\n");
4104         }
4105     }
4106
4107     /* Deal with midgame name changes */
4108     if (!newGame) {
4109         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4110             if (gameInfo.white) free(gameInfo.white);
4111             gameInfo.white = StrSave(white);
4112         }
4113         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4114             if (gameInfo.black) free(gameInfo.black);
4115             gameInfo.black = StrSave(black);
4116         }
4117     }
4118
4119     /* Throw away game result if anything actually changes in examine mode */
4120     if (gameMode == IcsExamining && !newGame) {
4121         gameInfo.result = GameUnfinished;
4122         if (gameInfo.resultDetails != NULL) {
4123             free(gameInfo.resultDetails);
4124             gameInfo.resultDetails = NULL;
4125         }
4126     }
4127
4128     /* In pausing && IcsExamining mode, we ignore boards coming
4129        in if they are in a different variation than we are. */
4130     if (pauseExamInvalid) return;
4131     if (pausing && gameMode == IcsExamining) {
4132         if (moveNum <= pauseExamForwardMostMove) {
4133             pauseExamInvalid = TRUE;
4134             forwardMostMove = pauseExamForwardMostMove;
4135             return;
4136         }
4137     }
4138
4139   if (appData.debugMode) {
4140     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4141   }
4142     /* Parse the board */
4143     for (k = 0; k < ranks; k++) {
4144       for (j = 0; j < files; j++)
4145         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4146       if(gameInfo.holdingsWidth > 1) {
4147            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4148            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4149       }
4150     }
4151     CopyBoard(boards[moveNum], board);
4152     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4153     if (moveNum == 0) {
4154         startedFromSetupPosition =
4155           !CompareBoards(board, initialPosition);
4156         if(startedFromSetupPosition)
4157             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4158     }
4159
4160     /* [HGM] Set castling rights. Take the outermost Rooks,
4161        to make it also work for FRC opening positions. Note that board12
4162        is really defective for later FRC positions, as it has no way to
4163        indicate which Rook can castle if they are on the same side of King.
4164        For the initial position we grant rights to the outermost Rooks,
4165        and remember thos rights, and we then copy them on positions
4166        later in an FRC game. This means WB might not recognize castlings with
4167        Rooks that have moved back to their original position as illegal,
4168        but in ICS mode that is not its job anyway.
4169     */
4170     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4171     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4172
4173         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4174             if(board[0][i] == WhiteRook) j = i;
4175         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4176         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4177             if(board[0][i] == WhiteRook) j = i;
4178         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4179         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4180             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4181         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4183             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4184         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4185
4186         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4187         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4188             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4189         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4190             if(board[BOARD_HEIGHT-1][k] == bKing)
4191                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4192         if(gameInfo.variant == VariantTwoKings) {
4193             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4194             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4195             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4196         }
4197     } else { int r;
4198         r = boards[moveNum][CASTLING][0] = initialRights[0];
4199         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4200         r = boards[moveNum][CASTLING][1] = initialRights[1];
4201         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4202         r = boards[moveNum][CASTLING][3] = initialRights[3];
4203         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4204         r = boards[moveNum][CASTLING][4] = initialRights[4];
4205         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4206         /* wildcastle kludge: always assume King has rights */
4207         r = boards[moveNum][CASTLING][2] = initialRights[2];
4208         r = boards[moveNum][CASTLING][5] = initialRights[5];
4209     }
4210     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4211     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4212
4213
4214     if (ics_getting_history == H_GOT_REQ_HEADER ||
4215         ics_getting_history == H_GOT_UNREQ_HEADER) {
4216         /* This was an initial position from a move list, not
4217            the current position */
4218         return;
4219     }
4220
4221     /* Update currentMove and known move number limits */
4222     newMove = newGame || moveNum > forwardMostMove;
4223
4224     if (newGame) {
4225         forwardMostMove = backwardMostMove = currentMove = moveNum;
4226         if (gameMode == IcsExamining && moveNum == 0) {
4227           /* Workaround for ICS limitation: we are not told the wild
4228              type when starting to examine a game.  But if we ask for
4229              the move list, the move list header will tell us */
4230             ics_getting_history = H_REQUESTED;
4231             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4232             SendToICS(str);
4233         }
4234     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4235                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4236 #if ZIPPY
4237         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4238         /* [HGM] applied this also to an engine that is silently watching        */
4239         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4240             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4241             gameInfo.variant == currentlyInitializedVariant) {
4242           takeback = forwardMostMove - moveNum;
4243           for (i = 0; i < takeback; i++) {
4244             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4245             SendToProgram("undo\n", &first);
4246           }
4247         }
4248 #endif
4249
4250         forwardMostMove = moveNum;
4251         if (!pausing || currentMove > forwardMostMove)
4252           currentMove = forwardMostMove;
4253     } else {
4254         /* New part of history that is not contiguous with old part */
4255         if (pausing && gameMode == IcsExamining) {
4256             pauseExamInvalid = TRUE;
4257             forwardMostMove = pauseExamForwardMostMove;
4258             return;
4259         }
4260         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4261 #if ZIPPY
4262             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4263                 // [HGM] when we will receive the move list we now request, it will be
4264                 // fed to the engine from the first move on. So if the engine is not
4265                 // in the initial position now, bring it there.
4266                 InitChessProgram(&first, 0);
4267             }
4268 #endif
4269             ics_getting_history = H_REQUESTED;
4270             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4271             SendToICS(str);
4272         }
4273         forwardMostMove = backwardMostMove = currentMove = moveNum;
4274     }
4275
4276     /* Update the clocks */
4277     if (strchr(elapsed_time, '.')) {
4278       /* Time is in ms */
4279       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4280       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4281     } else {
4282       /* Time is in seconds */
4283       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4284       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4285     }
4286
4287
4288 #if ZIPPY
4289     if (appData.zippyPlay && newGame &&
4290         gameMode != IcsObserving && gameMode != IcsIdle &&
4291         gameMode != IcsExamining)
4292       ZippyFirstBoard(moveNum, basetime, increment);
4293 #endif
4294
4295     /* Put the move on the move list, first converting
4296        to canonical algebraic form. */
4297     if (moveNum > 0) {
4298   if (appData.debugMode) {
4299     if (appData.debugMode) { int f = forwardMostMove;
4300         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4301                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4302                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4303     }
4304     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4305     fprintf(debugFP, "moveNum = %d\n", moveNum);
4306     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4307     setbuf(debugFP, NULL);
4308   }
4309         if (moveNum <= backwardMostMove) {
4310             /* We don't know what the board looked like before
4311                this move.  Punt. */
4312           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4313             strcat(parseList[moveNum - 1], " ");
4314             strcat(parseList[moveNum - 1], elapsed_time);
4315             moveList[moveNum - 1][0] = NULLCHAR;
4316         } else if (strcmp(move_str, "none") == 0) {
4317             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4318             /* Again, we don't know what the board looked like;
4319                this is really the start of the game. */
4320             parseList[moveNum - 1][0] = NULLCHAR;
4321             moveList[moveNum - 1][0] = NULLCHAR;
4322             backwardMostMove = moveNum;
4323             startedFromSetupPosition = TRUE;
4324             fromX = fromY = toX = toY = -1;
4325         } else {
4326           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4327           //                 So we parse the long-algebraic move string in stead of the SAN move
4328           int valid; char buf[MSG_SIZ], *prom;
4329
4330           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4331                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4332           // str looks something like "Q/a1-a2"; kill the slash
4333           if(str[1] == '/')
4334             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4335           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4336           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4337                 strcat(buf, prom); // long move lacks promo specification!
4338           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4339                 if(appData.debugMode)
4340                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4341                 safeStrCpy(move_str, buf, MSG_SIZ);
4342           }
4343           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4344                                 &fromX, &fromY, &toX, &toY, &promoChar)
4345                || ParseOneMove(buf, moveNum - 1, &moveType,
4346                                 &fromX, &fromY, &toX, &toY, &promoChar);
4347           // end of long SAN patch
4348           if (valid) {
4349             (void) CoordsToAlgebraic(boards[moveNum - 1],
4350                                      PosFlags(moveNum - 1),
4351                                      fromY, fromX, toY, toX, promoChar,
4352                                      parseList[moveNum-1]);
4353             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4354               case MT_NONE:
4355               case MT_STALEMATE:
4356               default:
4357                 break;
4358               case MT_CHECK:
4359                 if(gameInfo.variant != VariantShogi)
4360                     strcat(parseList[moveNum - 1], "+");
4361                 break;
4362               case MT_CHECKMATE:
4363               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4364                 strcat(parseList[moveNum - 1], "#");
4365                 break;
4366             }
4367             strcat(parseList[moveNum - 1], " ");
4368             strcat(parseList[moveNum - 1], elapsed_time);
4369             /* currentMoveString is set as a side-effect of ParseOneMove */
4370             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4371             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4372             strcat(moveList[moveNum - 1], "\n");
4373
4374             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4375                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4376               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4377                 ChessSquare old, new = boards[moveNum][k][j];
4378                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4379                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4380                   if(old == new) continue;
4381                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4382                   else if(new == WhiteWazir || new == BlackWazir) {
4383                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4384                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4385                       else boards[moveNum][k][j] = old; // preserve type of Gold
4386                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4387                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4388               }
4389           } else {
4390             /* Move from ICS was illegal!?  Punt. */
4391             if (appData.debugMode) {
4392               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4393               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4394             }
4395             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4396             strcat(parseList[moveNum - 1], " ");
4397             strcat(parseList[moveNum - 1], elapsed_time);
4398             moveList[moveNum - 1][0] = NULLCHAR;
4399             fromX = fromY = toX = toY = -1;
4400           }
4401         }
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4404     setbuf(debugFP, NULL);
4405   }
4406
4407 #if ZIPPY
4408         /* Send move to chess program (BEFORE animating it). */
4409         if (appData.zippyPlay && !newGame && newMove &&
4410            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4411
4412             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4413                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4414                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4415                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4416                             move_str);
4417                     DisplayError(str, 0);
4418                 } else {
4419                     if (first.sendTime) {
4420                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4421                     }
4422                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4423                     if (firstMove && !bookHit) {
4424                         firstMove = FALSE;
4425                         if (first.useColors) {
4426                           SendToProgram(gameMode == IcsPlayingWhite ?
4427                                         "white\ngo\n" :
4428                                         "black\ngo\n", &first);
4429                         } else {
4430                           SendToProgram("go\n", &first);
4431                         }
4432                         first.maybeThinking = TRUE;
4433                     }
4434                 }
4435             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4436               if (moveList[moveNum - 1][0] == NULLCHAR) {
4437                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4438                 DisplayError(str, 0);
4439               } else {
4440                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4441                 SendMoveToProgram(moveNum - 1, &first);
4442               }
4443             }
4444         }
4445 #endif
4446     }
4447
4448     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4449         /* If move comes from a remote source, animate it.  If it
4450            isn't remote, it will have already been animated. */
4451         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4452             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4453         }
4454         if (!pausing && appData.highlightLastMove) {
4455             SetHighlights(fromX, fromY, toX, toY);
4456         }
4457     }
4458
4459     /* Start the clocks */
4460     whiteFlag = blackFlag = FALSE;
4461     appData.clockMode = !(basetime == 0 && increment == 0);
4462     if (ticking == 0) {
4463       ics_clock_paused = TRUE;
4464       StopClocks();
4465     } else if (ticking == 1) {
4466       ics_clock_paused = FALSE;
4467     }
4468     if (gameMode == IcsIdle ||
4469         relation == RELATION_OBSERVING_STATIC ||
4470         relation == RELATION_EXAMINING ||
4471         ics_clock_paused)
4472       DisplayBothClocks();
4473     else
4474       StartClocks();
4475
4476     /* Display opponents and material strengths */
4477     if (gameInfo.variant != VariantBughouse &&
4478         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4479         if (tinyLayout || smallLayout) {
4480             if(gameInfo.variant == VariantNormal)
4481               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment);
4484             else
4485               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4486                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4487                     basetime, increment, (int) gameInfo.variant);
4488         } else {
4489             if(gameInfo.variant == VariantNormal)
4490               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment);
4493             else
4494               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4495                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4496                     basetime, increment, VariantName(gameInfo.variant));
4497         }
4498         DisplayTitle(str);
4499   if (appData.debugMode) {
4500     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4501   }
4502     }
4503
4504
4505     /* Display the board */
4506     if (!pausing && !appData.noGUI) {
4507
4508       if (appData.premove)
4509           if (!gotPremove ||
4510              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4511              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4512               ClearPremoveHighlights();
4513
4514       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4515         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4516       DrawPosition(j, boards[currentMove]);
4517
4518       DisplayMove(moveNum - 1);
4519       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4520             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4521               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4522         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4523       }
4524     }
4525
4526     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4527 #if ZIPPY
4528     if(bookHit) { // [HGM] book: simulate book reply
4529         static char bookMove[MSG_SIZ]; // a bit generous?
4530
4531         programStats.nodes = programStats.depth = programStats.time =
4532         programStats.score = programStats.got_only_move = 0;
4533         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4534
4535         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4536         strcat(bookMove, bookHit);
4537         HandleMachineMove(bookMove, &first);
4538     }
4539 #endif
4540 }
4541
4542 void
4543 GetMoveListEvent()
4544 {
4545     char buf[MSG_SIZ];
4546     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4547         ics_getting_history = H_REQUESTED;
4548         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4549         SendToICS(buf);
4550     }
4551 }
4552
4553 void
4554 AnalysisPeriodicEvent(force)
4555      int force;
4556 {
4557     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4558          && !force) || !appData.periodicUpdates)
4559       return;
4560
4561     /* Send . command to Crafty to collect stats */
4562     SendToProgram(".\n", &first);
4563
4564     /* Don't send another until we get a response (this makes
4565        us stop sending to old Crafty's which don't understand
4566        the "." command (sending illegal cmds resets node count & time,
4567        which looks bad)) */
4568     programStats.ok_to_send = 0;
4569 }
4570
4571 void ics_update_width(new_width)
4572         int new_width;
4573 {
4574         ics_printf("set width %d\n", new_width);
4575 }
4576
4577 void
4578 SendMoveToProgram(moveNum, cps)
4579      int moveNum;
4580      ChessProgramState *cps;
4581 {
4582     char buf[MSG_SIZ];
4583
4584     if (cps->useUsermove) {
4585       SendToProgram("usermove ", cps);
4586     }
4587     if (cps->useSAN) {
4588       char *space;
4589       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4590         int len = space - parseList[moveNum];
4591         memcpy(buf, parseList[moveNum], len);
4592         buf[len++] = '\n';
4593         buf[len] = NULLCHAR;
4594       } else {
4595         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4596       }
4597       SendToProgram(buf, cps);
4598     } else {
4599       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4600         AlphaRank(moveList[moveNum], 4);
4601         SendToProgram(moveList[moveNum], cps);
4602         AlphaRank(moveList[moveNum], 4); // and back
4603       } else
4604       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4605        * the engine. It would be nice to have a better way to identify castle
4606        * moves here. */
4607       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4608                                                                          && cps->useOOCastle) {
4609         int fromX = moveList[moveNum][0] - AAA;
4610         int fromY = moveList[moveNum][1] - ONE;
4611         int toX = moveList[moveNum][2] - AAA;
4612         int toY = moveList[moveNum][3] - ONE;
4613         if((boards[moveNum][fromY][fromX] == WhiteKing
4614             && boards[moveNum][toY][toX] == WhiteRook)
4615            || (boards[moveNum][fromY][fromX] == BlackKing
4616                && boards[moveNum][toY][toX] == BlackRook)) {
4617           if(toX > fromX) SendToProgram("O-O\n", cps);
4618           else SendToProgram("O-O-O\n", cps);
4619         }
4620         else SendToProgram(moveList[moveNum], cps);
4621       }
4622       else SendToProgram(moveList[moveNum], cps);
4623       /* End of additions by Tord */
4624     }
4625
4626     /* [HGM] setting up the opening has brought engine in force mode! */
4627     /*       Send 'go' if we are in a mode where machine should play. */
4628     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4629         (gameMode == TwoMachinesPlay   ||
4630 #if ZIPPY
4631          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4632 #endif
4633          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4634         SendToProgram("go\n", cps);
4635   if (appData.debugMode) {
4636     fprintf(debugFP, "(extra)\n");
4637   }
4638     }
4639     setboardSpoiledMachineBlack = 0;
4640 }
4641
4642 void
4643 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4644      ChessMove moveType;
4645      int fromX, fromY, toX, toY;
4646      char promoChar;
4647 {
4648     char user_move[MSG_SIZ];
4649
4650     switch (moveType) {
4651       default:
4652         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4653                 (int)moveType, fromX, fromY, toX, toY);
4654         DisplayError(user_move + strlen("say "), 0);
4655         break;
4656       case WhiteKingSideCastle:
4657       case BlackKingSideCastle:
4658       case WhiteQueenSideCastleWild:
4659       case BlackQueenSideCastleWild:
4660       /* PUSH Fabien */
4661       case WhiteHSideCastleFR:
4662       case BlackHSideCastleFR:
4663       /* POP Fabien */
4664         snprintf(user_move, MSG_SIZ, "o-o\n");
4665         break;
4666       case WhiteQueenSideCastle:
4667       case BlackQueenSideCastle:
4668       case WhiteKingSideCastleWild:
4669       case BlackKingSideCastleWild:
4670       /* PUSH Fabien */
4671       case WhiteASideCastleFR:
4672       case BlackASideCastleFR:
4673       /* POP Fabien */
4674         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4675         break;
4676       case WhiteNonPromotion:
4677       case BlackNonPromotion:
4678         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4679         break;
4680       case WhitePromotion:
4681       case BlackPromotion:
4682         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4683           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4684                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685                 PieceToChar(WhiteFerz));
4686         else if(gameInfo.variant == VariantGreat)
4687           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4688                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689                 PieceToChar(WhiteMan));
4690         else
4691           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4692                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4693                 promoChar);
4694         break;
4695       case WhiteDrop:
4696       case BlackDrop:
4697       drop:
4698         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4699                  ToUpper(PieceToChar((ChessSquare) fromX)),
4700                  AAA + toX, ONE + toY);
4701         break;
4702       case IllegalMove:  /* could be a variant we don't quite understand */
4703         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4704       case NormalMove:
4705       case WhiteCapturesEnPassant:
4706       case BlackCapturesEnPassant:
4707         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4708                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4709         break;
4710     }
4711     SendToICS(user_move);
4712     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4713         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4714 }
4715
4716 void
4717 UploadGameEvent()
4718 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4719     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4720     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4721     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4722         DisplayError("You cannot do this while you are playing or observing", 0);
4723         return;
4724     }
4725     if(gameMode != IcsExamining) { // is this ever not the case?
4726         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4727
4728         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4729           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4730         } else { // on FICS we must first go to general examine mode
4731           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4732         }
4733         if(gameInfo.variant != VariantNormal) {
4734             // try figure out wild number, as xboard names are not always valid on ICS
4735             for(i=1; i<=36; i++) {
4736               snprintf(buf, MSG_SIZ, "wild/%d", i);
4737                 if(StringToVariant(buf) == gameInfo.variant) break;
4738             }
4739             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4740             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4741             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4742         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4743         SendToICS(ics_prefix);
4744         SendToICS(buf);
4745         if(startedFromSetupPosition || backwardMostMove != 0) {
4746           fen = PositionToFEN(backwardMostMove, NULL);
4747           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4748             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4749             SendToICS(buf);
4750           } else { // FICS: everything has to set by separate bsetup commands
4751             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4752             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4753             SendToICS(buf);
4754             if(!WhiteOnMove(backwardMostMove)) {
4755                 SendToICS("bsetup tomove black\n");
4756             }
4757             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4758             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4759             SendToICS(buf);
4760             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4761             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4762             SendToICS(buf);
4763             i = boards[backwardMostMove][EP_STATUS];
4764             if(i >= 0) { // set e.p.
4765               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4766                 SendToICS(buf);
4767             }
4768             bsetup++;
4769           }
4770         }
4771       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4772             SendToICS("bsetup done\n"); // switch to normal examining.
4773     }
4774     for(i = backwardMostMove; i<last; i++) {
4775         char buf[20];
4776         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4777         SendToICS(buf);
4778     }
4779     SendToICS(ics_prefix);
4780     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4781 }
4782
4783 void
4784 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4785      int rf, ff, rt, ft;
4786      char promoChar;
4787      char move[7];
4788 {
4789     if (rf == DROP_RANK) {
4790       sprintf(move, "%c@%c%c\n",
4791                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4792     } else {
4793         if (promoChar == 'x' || promoChar == NULLCHAR) {
4794           sprintf(move, "%c%c%c%c\n",
4795                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4796         } else {
4797             sprintf(move, "%c%c%c%c%c\n",
4798                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4799         }
4800     }
4801 }
4802
4803 void
4804 ProcessICSInitScript(f)
4805      FILE *f;
4806 {
4807     char buf[MSG_SIZ];
4808
4809     while (fgets(buf, MSG_SIZ, f)) {
4810         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4811     }
4812
4813     fclose(f);
4814 }
4815
4816
4817 static int lastX, lastY, selectFlag, dragging, sweepX, sweepY;
4818 static ChessSquare substitute = EmptySquare;
4819 static char defaultPromoChar;
4820
4821 void
4822 Sweep(int step)
4823 {
4824     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4825     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4826     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4827     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4828     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4829     if(toY != BOARD_HEIGHT-1 && toY != 0) pawn = EmptySquare;
4830     do {
4831         promoSweep -= step;
4832         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4833         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4834         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4835         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4836         if(!step) step = 1;
4837     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4838             appData.testLegality && (promoSweep == king ||
4839             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4840     boards[currentMove][sweepY][sweepX] = promoSweep;
4841     DrawPosition(FALSE, boards[currentMove]);
4842 }
4843
4844 int PromoScroll(int x, int y)
4845 {
4846   int step = 0;
4847   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4848   if(!selectFlag) {
4849         if(y - lastY < 4 && lastY - y < 4) return FALSE; // assume dragging until significant distance
4850         if(substitute != EmptySquare && ((promoSweep >= BlackPawn) == flipView ? y <= lastY : y >= lastY)) { // we started dragging
4851             defaultPromoChar = ToLower(PieceToChar(promoSweep));   // fix choice
4852             promoSweep = EmptySquare;
4853             return FALSE;
4854         }
4855         DragPieceEnd(x, y); dragging = 0;
4856         selectFlag = 1; // we committed to sweep-selecting
4857   }
4858   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return TRUE;
4859   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4860   if(!step) return TRUE;
4861   lastX = x; lastY = y;
4862   Sweep(step);
4863   return TRUE;
4864 }
4865
4866 void
4867 NextPiece(int step)
4868 {
4869     ChessSquare piece = boards[currentMove][toY][toX];
4870     do {
4871         pieceSweep -= step;
4872         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4873         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4874         if(!step) step = -1;
4875     } while(PieceToChar(pieceSweep) == '.');
4876     boards[currentMove][toY][toX] = pieceSweep;
4877     DrawPosition(FALSE, boards[currentMove]);
4878     boards[currentMove][toY][toX] = piece;
4879 }
4880 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4881 void
4882 AlphaRank(char *move, int n)
4883 {
4884 //    char *p = move, c; int x, y;
4885
4886     if (appData.debugMode) {
4887         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4888     }
4889
4890     if(move[1]=='*' &&
4891        move[2]>='0' && move[2]<='9' &&
4892        move[3]>='a' && move[3]<='x'    ) {
4893         move[1] = '@';
4894         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4895         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4896     } else
4897     if(move[0]>='0' && move[0]<='9' &&
4898        move[1]>='a' && move[1]<='x' &&
4899        move[2]>='0' && move[2]<='9' &&
4900        move[3]>='a' && move[3]<='x'    ) {
4901         /* input move, Shogi -> normal */
4902         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4903         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4904         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4905         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4906     } else
4907     if(move[1]=='@' &&
4908        move[3]>='0' && move[3]<='9' &&
4909        move[2]>='a' && move[2]<='x'    ) {
4910         move[1] = '*';
4911         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4912         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4913     } else
4914     if(
4915        move[0]>='a' && move[0]<='x' &&
4916        move[3]>='0' && move[3]<='9' &&
4917        move[2]>='a' && move[2]<='x'    ) {
4918          /* output move, normal -> Shogi */
4919         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4920         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4921         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4922         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4923         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4924     }
4925     if (appData.debugMode) {
4926         fprintf(debugFP, "   out = '%s'\n", move);
4927     }
4928 }
4929
4930 char yy_textstr[8000];
4931
4932 /* Parser for moves from gnuchess, ICS, or user typein box */
4933 Boolean
4934 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4935      char *move;
4936      int moveNum;
4937      ChessMove *moveType;
4938      int *fromX, *fromY, *toX, *toY;
4939      char *promoChar;
4940 {
4941     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4942
4943     switch (*moveType) {
4944       case WhitePromotion:
4945       case BlackPromotion:
4946       case WhiteNonPromotion:
4947       case BlackNonPromotion:
4948       case NormalMove:
4949       case WhiteCapturesEnPassant:
4950       case BlackCapturesEnPassant:
4951       case WhiteKingSideCastle:
4952       case WhiteQueenSideCastle:
4953       case BlackKingSideCastle:
4954       case BlackQueenSideCastle:
4955       case WhiteKingSideCastleWild:
4956       case WhiteQueenSideCastleWild:
4957       case BlackKingSideCastleWild:
4958       case BlackQueenSideCastleWild:
4959       /* Code added by Tord: */
4960       case WhiteHSideCastleFR:
4961       case WhiteASideCastleFR:
4962       case BlackHSideCastleFR:
4963       case BlackASideCastleFR:
4964       /* End of code added by Tord */
4965       case IllegalMove:         /* bug or odd chess variant */
4966         *fromX = currentMoveString[0] - AAA;
4967         *fromY = currentMoveString[1] - ONE;
4968         *toX = currentMoveString[2] - AAA;
4969         *toY = currentMoveString[3] - ONE;
4970         *promoChar = currentMoveString[4];
4971         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4972             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4973     if (appData.debugMode) {
4974         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4975     }
4976             *fromX = *fromY = *toX = *toY = 0;
4977             return FALSE;
4978         }
4979         if (appData.testLegality) {
4980           return (*moveType != IllegalMove);
4981         } else {
4982           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4983                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4984         }
4985
4986       case WhiteDrop:
4987       case BlackDrop:
4988         *fromX = *moveType == WhiteDrop ?
4989           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4990           (int) CharToPiece(ToLower(currentMoveString[0]));
4991         *fromY = DROP_RANK;
4992         *toX = currentMoveString[2] - AAA;
4993         *toY = currentMoveString[3] - ONE;
4994         *promoChar = NULLCHAR;
4995         return TRUE;
4996
4997       case AmbiguousMove:
4998       case ImpossibleMove:
4999       case EndOfFile:
5000       case ElapsedTime:
5001       case Comment:
5002       case PGNTag:
5003       case NAG:
5004       case WhiteWins:
5005       case BlackWins:
5006       case GameIsDrawn:
5007       default:
5008     if (appData.debugMode) {
5009         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5010     }
5011         /* bug? */
5012         *fromX = *fromY = *toX = *toY = 0;
5013         *promoChar = NULLCHAR;
5014         return FALSE;
5015     }
5016 }
5017
5018
5019 void
5020 ParsePV(char *pv, Boolean storeComments)
5021 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5022   int fromX, fromY, toX, toY; char promoChar;
5023   ChessMove moveType;
5024   Boolean valid;
5025   int nr = 0;
5026
5027   endPV = forwardMostMove;
5028   do {
5029     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5030     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5031     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5032 if(appData.debugMode){
5033 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);
5034 }
5035     if(!valid && nr == 0 &&
5036        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5037         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5038         // Hande case where played move is different from leading PV move
5039         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5040         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5041         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5042         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5043           endPV += 2; // if position different, keep this
5044           moveList[endPV-1][0] = fromX + AAA;
5045           moveList[endPV-1][1] = fromY + ONE;
5046           moveList[endPV-1][2] = toX + AAA;
5047           moveList[endPV-1][3] = toY + ONE;
5048           parseList[endPV-1][0] = NULLCHAR;
5049           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5050         }
5051       }
5052     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5053     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5054     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5055     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5056         valid++; // allow comments in PV
5057         continue;
5058     }
5059     nr++;
5060     if(endPV+1 > framePtr) break; // no space, truncate
5061     if(!valid) break;
5062     endPV++;
5063     CopyBoard(boards[endPV], boards[endPV-1]);
5064     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5065     moveList[endPV-1][0] = fromX + AAA;
5066     moveList[endPV-1][1] = fromY + ONE;
5067     moveList[endPV-1][2] = toX + AAA;
5068     moveList[endPV-1][3] = toY + ONE;
5069     moveList[endPV-1][4] = promoChar;
5070     moveList[endPV-1][5] = NULLCHAR;
5071     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5072     if(storeComments)
5073         CoordsToAlgebraic(boards[endPV - 1],
5074                              PosFlags(endPV - 1),
5075                              fromY, fromX, toY, toX, promoChar,
5076                              parseList[endPV - 1]);
5077     else
5078         parseList[endPV-1][0] = NULLCHAR;
5079   } while(valid);
5080   currentMove = endPV;
5081   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5082   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5083                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5084   DrawPosition(TRUE, boards[currentMove]);
5085 }
5086
5087 Boolean
5088 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5089 {
5090         int startPV;
5091         char *p;
5092
5093         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5094         lastX = x; lastY = y;
5095         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5096         startPV = index;
5097         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5098         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5099         index = startPV;
5100         do{ while(buf[index] && buf[index] != '\n') index++;
5101         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5102         buf[index] = 0;
5103         ParsePV(buf+startPV, FALSE);
5104         *start = startPV; *end = index-1;
5105         return TRUE;
5106 }
5107
5108 Boolean
5109 LoadPV(int x, int y)
5110 { // called on right mouse click to load PV
5111   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5112   lastX = x; lastY = y;
5113   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5114   return TRUE;
5115 }
5116
5117 void
5118 UnLoadPV()
5119 {
5120   if(endPV < 0) return;
5121   endPV = -1;
5122   currentMove = forwardMostMove;
5123   ClearPremoveHighlights();
5124   DrawPosition(TRUE, boards[currentMove]);
5125 }
5126
5127 void
5128 MovePV(int x, int y, int h)
5129 { // step through PV based on mouse coordinates (called on mouse move)
5130   int margin = h>>3, step = 0, dist;
5131
5132   // we must somehow check if right button is still down (might be released off board!)
5133   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5134   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5135   if(!step) return;
5136   lastX = x; lastY = y;
5137
5138   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5139   if(endPV < 0) return;
5140   if(y < margin) step = 1; else
5141   if(y > h - margin) step = -1;
5142   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5143   currentMove += step;
5144   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5145   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5146                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5147   DrawPosition(FALSE, boards[currentMove]);
5148 }
5149
5150
5151 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5152 // All positions will have equal probability, but the current method will not provide a unique
5153 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5154 #define DARK 1
5155 #define LITE 2
5156 #define ANY 3
5157
5158 int squaresLeft[4];
5159 int piecesLeft[(int)BlackPawn];
5160 int seed, nrOfShuffles;
5161
5162 void GetPositionNumber()
5163 {       // sets global variable seed
5164         int i;
5165
5166         seed = appData.defaultFrcPosition;
5167         if(seed < 0) { // randomize based on time for negative FRC position numbers
5168                 for(i=0; i<50; i++) seed += random();
5169                 seed = random() ^ random() >> 8 ^ random() << 8;
5170                 if(seed<0) seed = -seed;
5171         }
5172 }
5173
5174 int put(Board board, int pieceType, int rank, int n, int shade)
5175 // put the piece on the (n-1)-th empty squares of the given shade
5176 {
5177         int i;
5178
5179         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5180                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5181                         board[rank][i] = (ChessSquare) pieceType;
5182                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5183                         squaresLeft[ANY]--;
5184                         piecesLeft[pieceType]--;
5185                         return i;
5186                 }
5187         }
5188         return -1;
5189 }
5190
5191
5192 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5193 // calculate where the next piece goes, (any empty square), and put it there
5194 {
5195         int i;
5196
5197         i = seed % squaresLeft[shade];
5198         nrOfShuffles *= squaresLeft[shade];
5199         seed /= squaresLeft[shade];
5200         put(board, pieceType, rank, i, shade);
5201 }
5202
5203 void AddTwoPieces(Board board, int pieceType, int rank)
5204 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5205 {
5206         int i, n=squaresLeft[ANY], j=n-1, k;
5207
5208         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5209         i = seed % k;  // pick one
5210         nrOfShuffles *= k;
5211         seed /= k;
5212         while(i >= j) i -= j--;
5213         j = n - 1 - j; i += j;
5214         put(board, pieceType, rank, j, ANY);
5215         put(board, pieceType, rank, i, ANY);
5216 }
5217
5218 void SetUpShuffle(Board board, int number)
5219 {
5220         int i, p, first=1;
5221
5222         GetPositionNumber(); nrOfShuffles = 1;
5223
5224         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5225         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5226         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5227
5228         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5229
5230         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5231             p = (int) board[0][i];
5232             if(p < (int) BlackPawn) piecesLeft[p] ++;
5233             board[0][i] = EmptySquare;
5234         }
5235
5236         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5237             // shuffles restricted to allow normal castling put KRR first
5238             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5239                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5240             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5241                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5242             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5243                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5244             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5245                 put(board, WhiteRook, 0, 0, ANY);
5246             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5247         }
5248
5249         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5250             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5251             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5252                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5253                 while(piecesLeft[p] >= 2) {
5254                     AddOnePiece(board, p, 0, LITE);
5255                     AddOnePiece(board, p, 0, DARK);
5256                 }
5257                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5258             }
5259
5260         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5261             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5262             // but we leave King and Rooks for last, to possibly obey FRC restriction
5263             if(p == (int)WhiteRook) continue;
5264             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5265             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5266         }
5267
5268         // now everything is placed, except perhaps King (Unicorn) and Rooks
5269
5270         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5271             // Last King gets castling rights
5272             while(piecesLeft[(int)WhiteUnicorn]) {
5273                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5274                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5275             }
5276
5277             while(piecesLeft[(int)WhiteKing]) {
5278                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5279                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5280             }
5281
5282
5283         } else {
5284             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5285             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5286         }
5287
5288         // Only Rooks can be left; simply place them all
5289         while(piecesLeft[(int)WhiteRook]) {
5290                 i = put(board, WhiteRook, 0, 0, ANY);
5291                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5292                         if(first) {
5293                                 first=0;
5294                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5295                         }
5296                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5297                 }
5298         }
5299         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5300             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5301         }
5302
5303         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5304 }
5305
5306 int SetCharTable( char *table, const char * map )
5307 /* [HGM] moved here from winboard.c because of its general usefulness */
5308 /*       Basically a safe strcpy that uses the last character as King */
5309 {
5310     int result = FALSE; int NrPieces;
5311
5312     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5313                     && NrPieces >= 12 && !(NrPieces&1)) {
5314         int i; /* [HGM] Accept even length from 12 to 34 */
5315
5316         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5317         for( i=0; i<NrPieces/2-1; i++ ) {
5318             table[i] = map[i];
5319             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5320         }
5321         table[(int) WhiteKing]  = map[NrPieces/2-1];
5322         table[(int) BlackKing]  = map[NrPieces-1];
5323
5324         result = TRUE;
5325     }
5326
5327     return result;
5328 }
5329
5330 void Prelude(Board board)
5331 {       // [HGM] superchess: random selection of exo-pieces
5332         int i, j, k; ChessSquare p;
5333         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5334
5335         GetPositionNumber(); // use FRC position number
5336
5337         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5338             SetCharTable(pieceToChar, appData.pieceToCharTable);
5339             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5340                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5341         }
5342
5343         j = seed%4;                 seed /= 4;
5344         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5345         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5346         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5347         j = seed%3 + (seed%3 >= j); seed /= 3;
5348         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5349         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5350         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5351         j = seed%3;                 seed /= 3;
5352         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5353         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5354         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5355         j = seed%2 + (seed%2 >= j); seed /= 2;
5356         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5357         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5358         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5359         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5360         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5361         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5362         put(board, exoPieces[0],    0, 0, ANY);
5363         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5364 }
5365
5366 void
5367 InitPosition(redraw)
5368      int redraw;
5369 {
5370     ChessSquare (* pieces)[BOARD_FILES];
5371     int i, j, pawnRow, overrule,
5372     oldx = gameInfo.boardWidth,
5373     oldy = gameInfo.boardHeight,
5374     oldh = gameInfo.holdingsWidth;
5375     static int oldv;
5376
5377     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5378
5379     /* [AS] Initialize pv info list [HGM] and game status */
5380     {
5381         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5382             pvInfoList[i].depth = 0;
5383             boards[i][EP_STATUS] = EP_NONE;
5384             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5385         }
5386
5387         initialRulePlies = 0; /* 50-move counter start */
5388
5389         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5390         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5391     }
5392
5393
5394     /* [HGM] logic here is completely changed. In stead of full positions */
5395     /* the initialized data only consist of the two backranks. The switch */
5396     /* selects which one we will use, which is than copied to the Board   */
5397     /* initialPosition, which for the rest is initialized by Pawns and    */
5398     /* empty squares. This initial position is then copied to boards[0],  */
5399     /* possibly after shuffling, so that it remains available.            */
5400
5401     gameInfo.holdingsWidth = 0; /* default board sizes */
5402     gameInfo.boardWidth    = 8;
5403     gameInfo.boardHeight   = 8;
5404     gameInfo.holdingsSize  = 0;
5405     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5406     for(i=0; i<BOARD_FILES-2; i++)
5407       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5408     initialPosition[EP_STATUS] = EP_NONE;
5409     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5410     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5411          SetCharTable(pieceNickName, appData.pieceNickNames);
5412     else SetCharTable(pieceNickName, "............");
5413     pieces = FIDEArray;
5414
5415     switch (gameInfo.variant) {
5416     case VariantFischeRandom:
5417       shuffleOpenings = TRUE;
5418     default:
5419       break;
5420     case VariantShatranj:
5421       pieces = ShatranjArray;
5422       nrCastlingRights = 0;
5423       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5424       break;
5425     case VariantMakruk:
5426       pieces = makrukArray;
5427       nrCastlingRights = 0;
5428       startedFromSetupPosition = TRUE;
5429       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5430       break;
5431     case VariantTwoKings:
5432       pieces = twoKingsArray;
5433       break;
5434     case VariantCapaRandom:
5435       shuffleOpenings = TRUE;
5436     case VariantCapablanca:
5437       pieces = CapablancaArray;
5438       gameInfo.boardWidth = 10;
5439       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5440       break;
5441     case VariantGothic:
5442       pieces = GothicArray;
5443       gameInfo.boardWidth = 10;
5444       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5445       break;
5446     case VariantSChess:
5447       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5448       gameInfo.holdingsSize = 7;
5449       break;
5450     case VariantJanus:
5451       pieces = JanusArray;
5452       gameInfo.boardWidth = 10;
5453       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5454       nrCastlingRights = 6;
5455         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5456         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5457         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5458         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5459         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5460         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5461       break;
5462     case VariantFalcon:
5463       pieces = FalconArray;
5464       gameInfo.boardWidth = 10;
5465       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5466       break;
5467     case VariantXiangqi:
5468       pieces = XiangqiArray;
5469       gameInfo.boardWidth  = 9;
5470       gameInfo.boardHeight = 10;
5471       nrCastlingRights = 0;
5472       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5473       break;
5474     case VariantShogi:
5475       pieces = ShogiArray;
5476       gameInfo.boardWidth  = 9;
5477       gameInfo.boardHeight = 9;
5478       gameInfo.holdingsSize = 7;
5479       nrCastlingRights = 0;
5480       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5481       break;
5482     case VariantCourier:
5483       pieces = CourierArray;
5484       gameInfo.boardWidth  = 12;
5485       nrCastlingRights = 0;
5486       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5487       break;
5488     case VariantKnightmate:
5489       pieces = KnightmateArray;
5490       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5491       break;
5492     case VariantSpartan:
5493       pieces = SpartanArray;
5494       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5495       break;
5496     case VariantFairy:
5497       pieces = fairyArray;
5498       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5499       break;
5500     case VariantGreat:
5501       pieces = GreatArray;
5502       gameInfo.boardWidth = 10;
5503       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5504       gameInfo.holdingsSize = 8;
5505       break;
5506     case VariantSuper:
5507       pieces = FIDEArray;
5508       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5509       gameInfo.holdingsSize = 8;
5510       startedFromSetupPosition = TRUE;
5511       break;
5512     case VariantCrazyhouse:
5513     case VariantBughouse:
5514       pieces = FIDEArray;
5515       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5516       gameInfo.holdingsSize = 5;
5517       break;
5518     case VariantWildCastle:
5519       pieces = FIDEArray;
5520       /* !!?shuffle with kings guaranteed to be on d or e file */
5521       shuffleOpenings = 1;
5522       break;
5523     case VariantNoCastle:
5524       pieces = FIDEArray;
5525       nrCastlingRights = 0;
5526       /* !!?unconstrained back-rank shuffle */
5527       shuffleOpenings = 1;
5528       break;
5529     }
5530
5531     overrule = 0;
5532     if(appData.NrFiles >= 0) {
5533         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5534         gameInfo.boardWidth = appData.NrFiles;
5535     }
5536     if(appData.NrRanks >= 0) {
5537         gameInfo.boardHeight = appData.NrRanks;
5538     }
5539     if(appData.holdingsSize >= 0) {
5540         i = appData.holdingsSize;
5541         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5542         gameInfo.holdingsSize = i;
5543     }
5544     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5545     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5546         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5547
5548     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5549     if(pawnRow < 1) pawnRow = 1;
5550     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5551
5552     /* User pieceToChar list overrules defaults */
5553     if(appData.pieceToCharTable != NULL)
5554         SetCharTable(pieceToChar, appData.pieceToCharTable);
5555
5556     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5557
5558         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5559             s = (ChessSquare) 0; /* account holding counts in guard band */
5560         for( i=0; i<BOARD_HEIGHT; i++ )
5561             initialPosition[i][j] = s;
5562
5563         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5564         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5565         initialPosition[pawnRow][j] = WhitePawn;
5566         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5567         if(gameInfo.variant == VariantXiangqi) {
5568             if(j&1) {
5569                 initialPosition[pawnRow][j] =
5570                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5571                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5572                    initialPosition[2][j] = WhiteCannon;
5573                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5574                 }
5575             }
5576         }
5577         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5578     }
5579     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5580
5581             j=BOARD_LEFT+1;
5582             initialPosition[1][j] = WhiteBishop;
5583             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5584             j=BOARD_RGHT-2;
5585             initialPosition[1][j] = WhiteRook;
5586             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5587     }
5588
5589     if( nrCastlingRights == -1) {
5590         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5591         /*       This sets default castling rights from none to normal corners   */
5592         /* Variants with other castling rights must set them themselves above    */
5593         nrCastlingRights = 6;
5594
5595         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5596         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5597         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5598         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5599         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5600         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5601      }
5602
5603      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5604      if(gameInfo.variant == VariantGreat) { // promotion commoners
5605         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5606         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5607         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5608         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5609      }
5610      if( gameInfo.variant == VariantSChess ) {
5611       initialPosition[1][0] = BlackMarshall;
5612       initialPosition[2][0] = BlackAngel;
5613       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5614       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5615       initialPosition[1][1] = initialPosition[2][1] = 
5616       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5617      }
5618   if (appData.debugMode) {
5619     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5620   }
5621     if(shuffleOpenings) {
5622         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5623         startedFromSetupPosition = TRUE;
5624     }
5625     if(startedFromPositionFile) {
5626       /* [HGM] loadPos: use PositionFile for every new game */
5627       CopyBoard(initialPosition, filePosition);
5628       for(i=0; i<nrCastlingRights; i++)
5629           initialRights[i] = filePosition[CASTLING][i];
5630       startedFromSetupPosition = TRUE;
5631     }
5632
5633     CopyBoard(boards[0], initialPosition);
5634
5635     if(oldx != gameInfo.boardWidth ||
5636        oldy != gameInfo.boardHeight ||
5637        oldv != gameInfo.variant ||
5638        oldh != gameInfo.holdingsWidth
5639                                          )
5640             InitDrawingSizes(-2 ,0);
5641
5642     oldv = gameInfo.variant;
5643     if (redraw)
5644       DrawPosition(TRUE, boards[currentMove]);
5645 }
5646
5647 void
5648 SendBoard(cps, moveNum)
5649      ChessProgramState *cps;
5650      int moveNum;
5651 {
5652     char message[MSG_SIZ];
5653
5654     if (cps->useSetboard) {
5655       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5656       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5657       SendToProgram(message, cps);
5658       free(fen);
5659
5660     } else {
5661       ChessSquare *bp;
5662       int i, j;
5663       /* Kludge to set black to move, avoiding the troublesome and now
5664        * deprecated "black" command.
5665        */
5666       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5667         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5668
5669       SendToProgram("edit\n", cps);
5670       SendToProgram("#\n", cps);
5671       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5672         bp = &boards[moveNum][i][BOARD_LEFT];
5673         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5674           if ((int) *bp < (int) BlackPawn) {
5675             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5676                     AAA + j, ONE + i);
5677             if(message[0] == '+' || message[0] == '~') {
5678               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5679                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5680                         AAA + j, ONE + i);
5681             }
5682             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5683                 message[1] = BOARD_RGHT   - 1 - j + '1';
5684                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5685             }
5686             SendToProgram(message, cps);
5687           }
5688         }
5689       }
5690
5691       SendToProgram("c\n", cps);
5692       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5693         bp = &boards[moveNum][i][BOARD_LEFT];
5694         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5695           if (((int) *bp != (int) EmptySquare)
5696               && ((int) *bp >= (int) BlackPawn)) {
5697             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5698                     AAA + j, ONE + i);
5699             if(message[0] == '+' || message[0] == '~') {
5700               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5701                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5702                         AAA + j, ONE + i);
5703             }
5704             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5705                 message[1] = BOARD_RGHT   - 1 - j + '1';
5706                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5707             }
5708             SendToProgram(message, cps);
5709           }
5710         }
5711       }
5712
5713       SendToProgram(".\n", cps);
5714     }
5715     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5716 }
5717
5718 ChessSquare
5719 DefaultPromoChoice(int white)
5720 {
5721     ChessSquare result;
5722     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5723         result = WhiteFerz; // no choice
5724     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5725         result= WhiteKing; // in Suicide Q is the last thing we want
5726     else if(gameInfo.variant == VariantSpartan)
5727         result = white ? WhiteQueen : WhiteAngel;
5728     else result = WhiteQueen;
5729     if(!white) result = WHITE_TO_BLACK result;
5730     return result;
5731 }
5732
5733 static int autoQueen; // [HGM] oneclick
5734
5735 int
5736 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5737 {
5738     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5739     /* [HGM] add Shogi promotions */
5740     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5741     ChessSquare piece;
5742     ChessMove moveType;
5743     Boolean premove;
5744
5745     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5746     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5747
5748     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5749       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5750         return FALSE;
5751
5752     piece = boards[currentMove][fromY][fromX];
5753     if(gameInfo.variant == VariantShogi) {
5754         promotionZoneSize = BOARD_HEIGHT/3;
5755         highestPromotingPiece = (int)WhiteFerz;
5756     } else if(gameInfo.variant == VariantMakruk) {
5757         promotionZoneSize = 3;
5758     }
5759
5760     // Treat Lance as Pawn when it is not representing Amazon
5761     if(gameInfo.variant != VariantSuper) {
5762         if(piece == WhiteLance) piece = WhitePawn; else
5763         if(piece == BlackLance) piece = BlackPawn;
5764     }
5765
5766     // next weed out all moves that do not touch the promotion zone at all
5767     if((int)piece >= BlackPawn) {
5768         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5769              return FALSE;
5770         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5771     } else {
5772         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5773            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5774     }
5775
5776     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5777
5778     // weed out mandatory Shogi promotions
5779     if(gameInfo.variant == VariantShogi) {
5780         if(piece >= BlackPawn) {
5781             if(toY == 0 && piece == BlackPawn ||
5782                toY == 0 && piece == BlackQueen ||
5783                toY <= 1 && piece == BlackKnight) {
5784                 *promoChoice = '+';
5785                 return FALSE;
5786             }
5787         } else {
5788             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5789                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5790                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5791                 *promoChoice = '+';
5792                 return FALSE;
5793             }
5794         }
5795     }
5796
5797     // weed out obviously illegal Pawn moves
5798     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5799         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5800         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5801         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5802         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5803         // note we are not allowed to test for valid (non-)capture, due to premove
5804     }
5805
5806     // we either have a choice what to promote to, or (in Shogi) whether to promote
5807     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5808         *promoChoice = PieceToChar(BlackFerz);  // no choice
5809         return FALSE;
5810     }
5811     // no sense asking what we must promote to if it is going to explode...
5812     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5813         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5814         return FALSE;
5815     }
5816     // with sweep-selection we take the selected default
5817     if(appData.sweepSelect) {
5818         *promoChoice = defaultPromoChar;
5819         return FALSE;
5820     }
5821     // give caller the default choice even if we will not make it
5822     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5823     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5824     if(autoQueen) return FALSE; // predetermined
5825
5826     // suppress promotion popup on illegal moves that are not premoves
5827     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5828               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5829     if(appData.testLegality && !premove) {
5830         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5831                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5832         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5833             return FALSE;
5834     }
5835
5836     return TRUE;
5837 }
5838
5839 int
5840 InPalace(row, column)
5841      int row, column;
5842 {   /* [HGM] for Xiangqi */
5843     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5844          column < (BOARD_WIDTH + 4)/2 &&
5845          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5846     return FALSE;
5847 }
5848
5849 int
5850 PieceForSquare (x, y)
5851      int x;
5852      int y;
5853 {
5854   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5855      return -1;
5856   else
5857      return boards[currentMove][y][x];
5858 }
5859
5860 int
5861 OKToStartUserMove(x, y)
5862      int x, y;
5863 {
5864     ChessSquare from_piece;
5865     int white_piece;
5866
5867     if (matchMode) return FALSE;
5868     if (gameMode == EditPosition) return TRUE;
5869
5870     if (x >= 0 && y >= 0)
5871       from_piece = boards[currentMove][y][x];
5872     else
5873       from_piece = EmptySquare;
5874
5875     if (from_piece == EmptySquare) return FALSE;
5876
5877     white_piece = (int)from_piece >= (int)WhitePawn &&
5878       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5879
5880     switch (gameMode) {
5881       case PlayFromGameFile:
5882       case AnalyzeFile:
5883       case TwoMachinesPlay:
5884       case EndOfGame:
5885         return FALSE;
5886
5887       case IcsObserving:
5888       case IcsIdle:
5889         return FALSE;
5890
5891       case MachinePlaysWhite:
5892       case IcsPlayingBlack:
5893         if (appData.zippyPlay) return FALSE;
5894         if (white_piece) {
5895             DisplayMoveError(_("You are playing Black"));
5896             return FALSE;
5897         }
5898         break;
5899
5900       case MachinePlaysBlack:
5901       case IcsPlayingWhite:
5902         if (appData.zippyPlay) return FALSE;
5903         if (!white_piece) {
5904             DisplayMoveError(_("You are playing White"));
5905             return FALSE;
5906         }
5907         break;
5908
5909       case EditGame:
5910         if (!white_piece && WhiteOnMove(currentMove)) {
5911             DisplayMoveError(_("It is White's turn"));
5912             return FALSE;
5913         }
5914         if (white_piece && !WhiteOnMove(currentMove)) {
5915             DisplayMoveError(_("It is Black's turn"));
5916             return FALSE;
5917         }
5918         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5919             /* Editing correspondence game history */
5920             /* Could disallow this or prompt for confirmation */
5921             cmailOldMove = -1;
5922         }
5923         break;
5924
5925       case BeginningOfGame:
5926         if (appData.icsActive) return FALSE;
5927         if (!appData.noChessProgram) {
5928             if (!white_piece) {
5929                 DisplayMoveError(_("You are playing White"));
5930                 return FALSE;
5931             }
5932         }
5933         break;
5934
5935       case Training:
5936         if (!white_piece && WhiteOnMove(currentMove)) {
5937             DisplayMoveError(_("It is White's turn"));
5938             return FALSE;
5939         }
5940         if (white_piece && !WhiteOnMove(currentMove)) {
5941             DisplayMoveError(_("It is Black's turn"));
5942             return FALSE;
5943         }
5944         break;
5945
5946       default:
5947       case IcsExamining:
5948         break;
5949     }
5950     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5951         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5952         && gameMode != AnalyzeFile && gameMode != Training) {
5953         DisplayMoveError(_("Displayed position is not current"));
5954         return FALSE;
5955     }
5956     return TRUE;
5957 }
5958
5959 Boolean
5960 OnlyMove(int *x, int *y, Boolean captures) {
5961     DisambiguateClosure cl;
5962     if (appData.zippyPlay) return FALSE;
5963     switch(gameMode) {
5964       case MachinePlaysBlack:
5965       case IcsPlayingWhite:
5966       case BeginningOfGame:
5967         if(!WhiteOnMove(currentMove)) return FALSE;
5968         break;
5969       case MachinePlaysWhite:
5970       case IcsPlayingBlack:
5971         if(WhiteOnMove(currentMove)) return FALSE;
5972         break;
5973       case EditGame:
5974         break;
5975       default:
5976         return FALSE;
5977     }
5978     cl.pieceIn = EmptySquare;
5979     cl.rfIn = *y;
5980     cl.ffIn = *x;
5981     cl.rtIn = -1;
5982     cl.ftIn = -1;
5983     cl.promoCharIn = NULLCHAR;
5984     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5985     if( cl.kind == NormalMove ||
5986         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5987         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5988         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5989       fromX = cl.ff;
5990       fromY = cl.rf;
5991       *x = cl.ft;
5992       *y = cl.rt;
5993       return TRUE;
5994     }
5995     if(cl.kind != ImpossibleMove) return FALSE;
5996     cl.pieceIn = EmptySquare;
5997     cl.rfIn = -1;
5998     cl.ffIn = -1;
5999     cl.rtIn = *y;
6000     cl.ftIn = *x;
6001     cl.promoCharIn = NULLCHAR;
6002     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6003     if( cl.kind == NormalMove ||
6004         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6005         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6006         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6007       fromX = cl.ff;
6008       fromY = cl.rf;
6009       *x = cl.ft;
6010       *y = cl.rt;
6011       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6012       return TRUE;
6013     }
6014     return FALSE;
6015 }
6016
6017 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6018 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6019 int lastLoadGameUseList = FALSE;
6020 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6021 ChessMove lastLoadGameStart = EndOfFile;
6022
6023 void
6024 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6025      int fromX, fromY, toX, toY;
6026      int promoChar;
6027 {
6028     ChessMove moveType;
6029     ChessSquare pdown, pup;
6030
6031     /* Check if the user is playing in turn.  This is complicated because we
6032        let the user "pick up" a piece before it is his turn.  So the piece he
6033        tried to pick up may have been captured by the time he puts it down!
6034        Therefore we use the color the user is supposed to be playing in this
6035        test, not the color of the piece that is currently on the starting
6036        square---except in EditGame mode, where the user is playing both
6037        sides; fortunately there the capture race can't happen.  (It can
6038        now happen in IcsExamining mode, but that's just too bad.  The user
6039        will get a somewhat confusing message in that case.)
6040        */
6041
6042     switch (gameMode) {
6043       case PlayFromGameFile:
6044       case AnalyzeFile:
6045       case TwoMachinesPlay:
6046       case EndOfGame:
6047       case IcsObserving:
6048       case IcsIdle:
6049         /* We switched into a game mode where moves are not accepted,
6050            perhaps while the mouse button was down. */
6051         return;
6052
6053       case MachinePlaysWhite:
6054         /* User is moving for Black */
6055         if (WhiteOnMove(currentMove)) {
6056             DisplayMoveError(_("It is White's turn"));
6057             return;
6058         }
6059         break;
6060
6061       case MachinePlaysBlack:
6062         /* User is moving for White */
6063         if (!WhiteOnMove(currentMove)) {
6064             DisplayMoveError(_("It is Black's turn"));
6065             return;
6066         }
6067         break;
6068
6069       case EditGame:
6070       case IcsExamining:
6071       case BeginningOfGame:
6072       case AnalyzeMode:
6073       case Training:
6074         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6075         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6076             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6077             /* User is moving for Black */
6078             if (WhiteOnMove(currentMove)) {
6079                 DisplayMoveError(_("It is White's turn"));
6080                 return;
6081             }
6082         } else {
6083             /* User is moving for White */
6084             if (!WhiteOnMove(currentMove)) {
6085                 DisplayMoveError(_("It is Black's turn"));
6086                 return;
6087             }
6088         }
6089         break;
6090
6091       case IcsPlayingBlack:
6092         /* User is moving for Black */
6093         if (WhiteOnMove(currentMove)) {
6094             if (!appData.premove) {
6095                 DisplayMoveError(_("It is White's turn"));
6096             } else if (toX >= 0 && toY >= 0) {
6097                 premoveToX = toX;
6098                 premoveToY = toY;
6099                 premoveFromX = fromX;
6100                 premoveFromY = fromY;
6101                 premovePromoChar = promoChar;
6102                 gotPremove = 1;
6103                 if (appData.debugMode)
6104                     fprintf(debugFP, "Got premove: fromX %d,"
6105                             "fromY %d, toX %d, toY %d\n",
6106                             fromX, fromY, toX, toY);
6107             }
6108             return;
6109         }
6110         break;
6111
6112       case IcsPlayingWhite:
6113         /* User is moving for White */
6114         if (!WhiteOnMove(currentMove)) {
6115             if (!appData.premove) {
6116                 DisplayMoveError(_("It is Black's turn"));
6117             } else if (toX >= 0 && toY >= 0) {
6118                 premoveToX = toX;
6119                 premoveToY = toY;
6120                 premoveFromX = fromX;
6121                 premoveFromY = fromY;
6122                 premovePromoChar = promoChar;
6123                 gotPremove = 1;
6124                 if (appData.debugMode)
6125                     fprintf(debugFP, "Got premove: fromX %d,"
6126                             "fromY %d, toX %d, toY %d\n",
6127                             fromX, fromY, toX, toY);
6128             }
6129             return;
6130         }
6131         break;
6132
6133       default:
6134         break;
6135
6136       case EditPosition:
6137         /* EditPosition, empty square, or different color piece;
6138            click-click move is possible */
6139         if (toX == -2 || toY == -2) {
6140             boards[0][fromY][fromX] = EmptySquare;
6141             DrawPosition(FALSE, boards[currentMove]);
6142             return;
6143         } else if (toX >= 0 && toY >= 0) {
6144             boards[0][toY][toX] = boards[0][fromY][fromX];
6145             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6146                 if(boards[0][fromY][0] != EmptySquare) {
6147                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6148                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6149                 }
6150             } else
6151             if(fromX == BOARD_RGHT+1) {
6152                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6153                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6154                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6155                 }
6156             } else
6157             boards[0][fromY][fromX] = EmptySquare;
6158             DrawPosition(FALSE, boards[currentMove]);
6159             return;
6160         }
6161         return;
6162     }
6163
6164     if(toX < 0 || toY < 0) return;
6165     pdown = boards[currentMove][fromY][fromX];
6166     pup = boards[currentMove][toY][toX];
6167
6168     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6169     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6170          if( pup != EmptySquare ) return;
6171          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6172            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6173                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6174            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6175            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6176            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6177            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6178          fromY = DROP_RANK;
6179     }
6180
6181     /* [HGM] always test for legality, to get promotion info */
6182     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6183                                          fromY, fromX, toY, toX, promoChar);
6184     /* [HGM] but possibly ignore an IllegalMove result */
6185     if (appData.testLegality) {
6186         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6187             DisplayMoveError(_("Illegal move"));
6188             return;
6189         }
6190     }
6191
6192     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6193 }
6194
6195 /* Common tail of UserMoveEvent and DropMenuEvent */
6196 int
6197 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6198      ChessMove moveType;
6199      int fromX, fromY, toX, toY;
6200      /*char*/int promoChar;
6201 {
6202     char *bookHit = 0;
6203
6204     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6205         // [HGM] superchess: suppress promotions to non-available piece
6206         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6207         if(WhiteOnMove(currentMove)) {
6208             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6209         } else {
6210             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6211         }
6212     }
6213
6214     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6215        move type in caller when we know the move is a legal promotion */
6216     if(moveType == NormalMove && promoChar)
6217         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6218
6219     /* [HGM] <popupFix> The following if has been moved here from
6220        UserMoveEvent(). Because it seemed to belong here (why not allow
6221        piece drops in training games?), and because it can only be
6222        performed after it is known to what we promote. */
6223     if (gameMode == Training) {
6224       /* compare the move played on the board to the next move in the
6225        * game. If they match, display the move and the opponent's response.
6226        * If they don't match, display an error message.
6227        */
6228       int saveAnimate;
6229       Board testBoard;
6230       CopyBoard(testBoard, boards[currentMove]);
6231       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6232
6233       if (CompareBoards(testBoard, boards[currentMove+1])) {
6234         ForwardInner(currentMove+1);
6235
6236         /* Autoplay the opponent's response.
6237          * if appData.animate was TRUE when Training mode was entered,
6238          * the response will be animated.
6239          */
6240         saveAnimate = appData.animate;
6241         appData.animate = animateTraining;
6242         ForwardInner(currentMove+1);
6243         appData.animate = saveAnimate;
6244
6245         /* check for the end of the game */
6246         if (currentMove >= forwardMostMove) {
6247           gameMode = PlayFromGameFile;
6248           ModeHighlight();
6249           SetTrainingModeOff();
6250           DisplayInformation(_("End of game"));
6251         }
6252       } else {
6253         DisplayError(_("Incorrect move"), 0);
6254       }
6255       return 1;
6256     }
6257
6258   /* Ok, now we know that the move is good, so we can kill
6259      the previous line in Analysis Mode */
6260   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6261                                 && currentMove < forwardMostMove) {
6262     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6263     else forwardMostMove = currentMove;
6264   }
6265
6266   /* If we need the chess program but it's dead, restart it */
6267   ResurrectChessProgram();
6268
6269   /* A user move restarts a paused game*/
6270   if (pausing)
6271     PauseEvent();
6272
6273   thinkOutput[0] = NULLCHAR;
6274
6275   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6276
6277   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6278     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6279     return 1;
6280   }
6281
6282   if (gameMode == BeginningOfGame) {
6283     if (appData.noChessProgram) {
6284       gameMode = EditGame;
6285       SetGameInfo();
6286     } else {
6287       char buf[MSG_SIZ];
6288       gameMode = MachinePlaysBlack;
6289       StartClocks();
6290       SetGameInfo();
6291       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6292       DisplayTitle(buf);
6293       if (first.sendName) {
6294         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6295         SendToProgram(buf, &first);
6296       }
6297       StartClocks();
6298     }
6299     ModeHighlight();
6300   }
6301
6302   /* Relay move to ICS or chess engine */
6303   if (appData.icsActive) {
6304     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6305         gameMode == IcsExamining) {
6306       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6307         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6308         SendToICS("draw ");
6309         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6310       }
6311       // also send plain move, in case ICS does not understand atomic claims
6312       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6313       ics_user_moved = 1;
6314     }
6315   } else {
6316     if (first.sendTime && (gameMode == BeginningOfGame ||
6317                            gameMode == MachinePlaysWhite ||
6318                            gameMode == MachinePlaysBlack)) {
6319       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6320     }
6321     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6322          // [HGM] book: if program might be playing, let it use book
6323         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6324         first.maybeThinking = TRUE;
6325     } else SendMoveToProgram(forwardMostMove-1, &first);
6326     if (currentMove == cmailOldMove + 1) {
6327       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6328     }
6329   }
6330
6331   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6332
6333   switch (gameMode) {
6334   case EditGame:
6335     if(appData.testLegality)
6336     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6337     case MT_NONE:
6338     case MT_CHECK:
6339       break;
6340     case MT_CHECKMATE:
6341     case MT_STAINMATE:
6342       if (WhiteOnMove(currentMove)) {
6343         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6344       } else {
6345         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6346       }
6347       break;
6348     case MT_STALEMATE:
6349       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6350       break;
6351     }
6352     break;
6353
6354   case MachinePlaysBlack:
6355   case MachinePlaysWhite:
6356     /* disable certain menu options while machine is thinking */
6357     SetMachineThinkingEnables();
6358     break;
6359
6360   default:
6361     break;
6362   }
6363
6364   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6365   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6366
6367   if(bookHit) { // [HGM] book: simulate book reply
6368         static char bookMove[MSG_SIZ]; // a bit generous?
6369
6370         programStats.nodes = programStats.depth = programStats.time =
6371         programStats.score = programStats.got_only_move = 0;
6372         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6373
6374         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6375         strcat(bookMove, bookHit);
6376         HandleMachineMove(bookMove, &first);
6377   }
6378   return 1;
6379 }
6380
6381 void
6382 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6383      Board board;
6384      int flags;
6385      ChessMove kind;
6386      int rf, ff, rt, ft;
6387      VOIDSTAR closure;
6388 {
6389     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6390     Markers *m = (Markers *) closure;
6391     if(rf == fromY && ff == fromX)
6392         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6393                          || kind == WhiteCapturesEnPassant
6394                          || kind == BlackCapturesEnPassant);
6395     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6396 }
6397
6398 void
6399 MarkTargetSquares(int clear)
6400 {
6401   int x, y;
6402   if(!appData.markers || !appData.highlightDragging ||
6403      !appData.testLegality || gameMode == EditPosition) return;
6404   if(clear) {
6405     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6406   } else {
6407     int capt = 0;
6408     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6409     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6410       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6411       if(capt)
6412       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6413     }
6414   }
6415   DrawPosition(TRUE, NULL);
6416 }
6417
6418 int
6419 Explode(Board board, int fromX, int fromY, int toX, int toY)
6420 {
6421     if(gameInfo.variant == VariantAtomic &&
6422        (board[toY][toX] != EmptySquare ||                     // capture?
6423         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6424                          board[fromY][fromX] == BlackPawn   )
6425       )) {
6426         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6427         return TRUE;
6428     }
6429     return FALSE;
6430 }
6431
6432 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6433
6434 int CanPromote(ChessSquare piece, int y)
6435 {
6436         return (piece == BlackPawn && y == 1 ||
6437                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6438                 gameInfo.variant != VariantSuper && 
6439                         (piece == BlackLance && y == 1 ||
6440                          piece == WhiteLance && y == BOARD_HEIGHT-2) );
6441 }
6442
6443 void LeftClick(ClickType clickType, int xPix, int yPix)
6444 {
6445     int x, y, canPromote;
6446     Boolean saveAnimate;
6447     static int second = 0, promotionChoice = 0;
6448     char promoChoice = NULLCHAR;
6449     ChessSquare piece;
6450
6451     if(appData.seekGraph && appData.icsActive && loggedOn &&
6452         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6453         SeekGraphClick(clickType, xPix, yPix, 0);
6454         return;
6455     }
6456
6457     if (clickType == Press) ErrorPopDown();
6458     MarkTargetSquares(1);
6459
6460     x = EventToSquare(xPix, BOARD_WIDTH);
6461     y = EventToSquare(yPix, BOARD_HEIGHT);
6462     if (!flipView && y >= 0) {
6463         y = BOARD_HEIGHT - 1 - y;
6464     }
6465     if (flipView && x >= 0) {
6466         x = BOARD_WIDTH - 1 - x;
6467     }
6468
6469     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6470         defaultPromoChar = ToLower(PieceToChar(defaultPromoChoice = promoSweep));
6471         if(gameInfo.variant == VariantShogi) defaultPromoChar = (promoSweep == boards[currentMove][fromY][fromX] ? '=' : '+');
6472         promoSweep = EmptySquare;   // terminate sweep
6473         promoDefaultAltered = TRUE;
6474         if(savePiece != EmptySquare) {
6475             boards[currentMove][sweepY][sweepX] = savePiece; savePiece = EmptySquare;
6476             clickType = Press; x = toX; y = toY; // fake up-click on to-square to finish one-click move
6477         } else x = fromX, y = fromY;             // and fake up-click on same square otherwise
6478     }
6479
6480     if(substitute != EmptySquare) {
6481         boards[currentMove][fromY][fromX] = substitute;
6482         substitute = EmptySquare;
6483         DragPieceEnd(xPix, yPix); dragging = 0;
6484         DrawPosition(FALSE, boards[currentMove]);
6485     }
6486
6487     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6488         if(clickType == Release) return; // ignore upclick of click-click destination
6489         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6490         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6491         if(gameInfo.holdingsWidth &&
6492                 (WhiteOnMove(currentMove)
6493                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6494                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6495             // click in right holdings, for determining promotion piece
6496             ChessSquare p = boards[currentMove][y][x];
6497             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6498             if(p != EmptySquare) {
6499                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6500                 fromX = fromY = -1;
6501                 return;
6502             }
6503         }
6504         DrawPosition(FALSE, boards[currentMove]);
6505         return;
6506     }
6507
6508     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6509     if(clickType == Press
6510             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6511               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6512               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6513         return;
6514
6515     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6516         fromX = fromY = -1; // second click on piece after altering default treated as first click
6517
6518    if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6519         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6520                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6521         defaultPromoChoice = DefaultPromoChoice(side);
6522    }
6523
6524     autoQueen = appData.alwaysPromoteToQueen;
6525
6526     if (fromX == -1) {
6527       gatingPiece = EmptySquare;
6528       if (clickType != Press) {
6529         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6530             DragPieceEnd(xPix, yPix); dragging = 0;
6531             DrawPosition(FALSE, NULL);
6532         }
6533         return;
6534       }
6535       if(appData.oneClick && OnlyMove(&x, &y, FALSE)) {
6536             if(appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY)) {
6537                 promoSweep = defaultPromoChoice;
6538                 savePiece = boards[currentMove][sweepY = toY = y][sweepX = toX = x];
6539                 selectFlag = 0; lastX = xPix; lastY = yPix;
6540                 Sweep(0); // Pawn that is going to promote: preview promotion piece
6541                 return;
6542             }
6543       } else {
6544             /* First square */
6545             if (OKToStartUserMove(x, y)) {
6546                 sweepX = fromX = x;
6547                 sweepY = fromY = y;
6548                 second = 0;
6549                 MarkTargetSquares(0);
6550                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6551                     promoSweep = defaultPromoChoice;
6552                     substitute = piece; selectFlag = 0; lastX = xPix; lastY = yPix;
6553                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6554                 }
6555                 DragPieceBegin(xPix, yPix); dragging = 1;
6556                 if (appData.highlightDragging) {
6557                     SetHighlights(x, y, -1, -1);
6558                 }
6559             }
6560             return;
6561         }
6562     }
6563
6564     /* fromX != -1 */
6565     if (clickType == Press && gameMode != EditPosition) {
6566         ChessSquare fromP;
6567         ChessSquare toP;
6568         int frc;
6569
6570         // ignore off-board to clicks
6571         if(y < 0 || x < 0) return;
6572
6573         /* Check if clicking again on the same color piece */
6574         fromP = boards[currentMove][fromY][fromX];
6575         toP = boards[currentMove][y][x];
6576         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6577         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6578              WhitePawn <= toP && toP <= WhiteKing &&
6579              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6580              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6581             (BlackPawn <= fromP && fromP <= BlackKing &&
6582              BlackPawn <= toP && toP <= BlackKing &&
6583              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6584              !(fromP == BlackKing && toP == BlackRook && frc))) {
6585             /* Clicked again on same color piece -- changed his mind */
6586             second = (x == fromX && y == fromY);
6587             promoDefaultAltered = FALSE;
6588            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6589             if (appData.highlightDragging) {
6590                 SetHighlights(x, y, -1, -1);
6591             } else {
6592                 ClearHighlights();
6593             }
6594             if (OKToStartUserMove(x, y)) {
6595                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6596                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6597                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6598                  gatingPiece = boards[currentMove][fromY][fromX];
6599                 else gatingPiece = EmptySquare;
6600                 sweepX = fromX = x;
6601                 sweepY = fromY = y; dragging = 1;
6602                 MarkTargetSquares(0);
6603                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6604                     promoSweep = defaultPromoChoice;
6605                     substitute = piece; selectFlag = 0; lastX = xPix; lastY = yPix;
6606                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6607                 }
6608                 DragPieceBegin(xPix, yPix);
6609             }
6610            }
6611            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6612            second = FALSE; 
6613         }
6614         // ignore clicks on holdings
6615         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6616     }
6617
6618     if (clickType == Release && x == fromX && y == fromY) {
6619         DragPieceEnd(xPix, yPix); dragging = 0;
6620         if (appData.animateDragging) {
6621             /* Undo animation damage if any */
6622             DrawPosition(FALSE, NULL);
6623         }
6624         if (second) {
6625             /* Second up/down in same square; just abort move */
6626             second = 0;
6627             fromX = fromY = -1;
6628             gatingPiece = EmptySquare;
6629             ClearHighlights();
6630             gotPremove = 0;
6631             ClearPremoveHighlights();
6632         } else {
6633             /* First upclick in same square; start click-click mode */
6634             SetHighlights(x, y, -1, -1);
6635         }
6636         return;
6637     }
6638
6639     /* we now have a different from- and (possibly off-board) to-square */
6640     /* Completed move */
6641     toX = x;
6642     toY = y;
6643     saveAnimate = appData.animate;
6644     if (clickType == Press) {
6645         /* Finish clickclick move */
6646         if (appData.animate || appData.highlightLastMove) {
6647             SetHighlights(fromX, fromY, toX, toY);
6648         } else {
6649             ClearHighlights();
6650         }
6651     } else {
6652         /* Finish drag move */
6653         if (appData.highlightLastMove) {
6654             SetHighlights(fromX, fromY, toX, toY);
6655         } else {
6656             ClearHighlights();
6657         }
6658         DragPieceEnd(xPix, yPix); dragging = 0;
6659         /* Don't animate move and drag both */
6660         appData.animate = FALSE;
6661     }
6662
6663     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6664     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6665         ChessSquare piece = boards[currentMove][fromY][fromX];
6666         if(gameMode == EditPosition && piece != EmptySquare &&
6667            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6668             int n;
6669
6670             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6671                 n = PieceToNumber(piece - (int)BlackPawn);
6672                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6673                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6674                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6675             } else
6676             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6677                 n = PieceToNumber(piece);
6678                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6679                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6680                 boards[currentMove][n][BOARD_WIDTH-2]++;
6681             }
6682             boards[currentMove][fromY][fromX] = EmptySquare;
6683         }
6684         ClearHighlights();
6685         fromX = fromY = -1;
6686         DrawPosition(TRUE, boards[currentMove]);
6687         return;
6688     }
6689
6690     // off-board moves should not be highlighted
6691     if(x < 0 || y < 0) ClearHighlights();
6692
6693     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6694
6695     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6696         SetHighlights(fromX, fromY, toX, toY);
6697         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6698             // [HGM] super: promotion to captured piece selected from holdings
6699             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6700             promotionChoice = TRUE;
6701             // kludge follows to temporarily execute move on display, without promoting yet
6702             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6703             boards[currentMove][toY][toX] = p;
6704             DrawPosition(FALSE, boards[currentMove]);
6705             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6706             boards[currentMove][toY][toX] = q;
6707             DisplayMessage("Click in holdings to choose piece", "");
6708             return;
6709         }
6710         if(appData.sweepSelect && clickType == Press) {
6711              lastX = xPix; lastY = yPix;
6712              ChessSquare piece = boards[currentMove][fromY][fromX];
6713              promoSweep = defaultPromoChoice;
6714              if(gameInfo.variant == VariantShogi) promoSweep = PROMOTED piece;
6715              sweepX = toX; sweepY = toY;
6716              selectFlag = 1; Sweep(0);
6717         } else PromotionPopUp();
6718     } else {
6719         int oldMove = currentMove;
6720         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6721         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6722         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6723         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6724            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6725             DrawPosition(TRUE, boards[currentMove]);
6726         fromX = fromY = -1;
6727     }
6728     appData.animate = saveAnimate;
6729     if (appData.animate || appData.animateDragging) {
6730         /* Undo animation damage if needed */
6731         DrawPosition(FALSE, NULL);
6732     }
6733 }
6734
6735 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6736 {   // front-end-free part taken out of PieceMenuPopup
6737     int whichMenu; int xSqr, ySqr;
6738
6739     if(seekGraphUp) { // [HGM] seekgraph
6740         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6741         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6742         return -2;
6743     }
6744
6745     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6746          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6747         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6748         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6749         if(action == Press)   {
6750             originalFlip = flipView;
6751             flipView = !flipView; // temporarily flip board to see game from partners perspective
6752             DrawPosition(TRUE, partnerBoard);
6753             DisplayMessage(partnerStatus, "");
6754             partnerUp = TRUE;
6755         } else if(action == Release) {
6756             flipView = originalFlip;
6757             DrawPosition(TRUE, boards[currentMove]);
6758             partnerUp = FALSE;
6759         }
6760         return -2;
6761     }
6762
6763     xSqr = EventToSquare(x, BOARD_WIDTH);
6764     ySqr = EventToSquare(y, BOARD_HEIGHT);
6765     if (action == Release) {
6766         if(pieceSweep != EmptySquare) {
6767             EditPositionMenuEvent(pieceSweep, toX, toY);
6768             pieceSweep = EmptySquare;
6769         } else UnLoadPV(); // [HGM] pv
6770     }
6771     if (action != Press) return -2; // return code to be ignored
6772     switch (gameMode) {
6773       case IcsExamining:
6774         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6775       case EditPosition:
6776         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6777         if (xSqr < 0 || ySqr < 0) return -1;
6778         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6779         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6780         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6781         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6782         NextPiece(0);
6783         return -2;\r
6784       case IcsObserving:
6785         if(!appData.icsEngineAnalyze) return -1;
6786       case IcsPlayingWhite:
6787       case IcsPlayingBlack:
6788         if(!appData.zippyPlay) goto noZip;
6789       case AnalyzeMode:
6790       case AnalyzeFile:
6791       case MachinePlaysWhite:
6792       case MachinePlaysBlack:
6793       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6794         if (!appData.dropMenu) {
6795           LoadPV(x, y);
6796           return 2; // flag front-end to grab mouse events
6797         }
6798         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6799            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6800       case EditGame:
6801       noZip:
6802         if (xSqr < 0 || ySqr < 0) return -1;
6803         if (!appData.dropMenu || appData.testLegality &&
6804             gameInfo.variant != VariantBughouse &&
6805             gameInfo.variant != VariantCrazyhouse) return -1;
6806         whichMenu = 1; // drop menu
6807         break;
6808       default:
6809         return -1;
6810     }
6811
6812     if (((*fromX = xSqr) < 0) ||
6813         ((*fromY = ySqr) < 0)) {
6814         *fromX = *fromY = -1;
6815         return -1;
6816     }
6817     if (flipView)
6818       *fromX = BOARD_WIDTH - 1 - *fromX;
6819     else
6820       *fromY = BOARD_HEIGHT - 1 - *fromY;
6821
6822     return whichMenu;
6823 }
6824
6825 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6826 {
6827 //    char * hint = lastHint;
6828     FrontEndProgramStats stats;
6829
6830     stats.which = cps == &first ? 0 : 1;
6831     stats.depth = cpstats->depth;
6832     stats.nodes = cpstats->nodes;
6833     stats.score = cpstats->score;
6834     stats.time = cpstats->time;
6835     stats.pv = cpstats->movelist;
6836     stats.hint = lastHint;
6837     stats.an_move_index = 0;
6838     stats.an_move_count = 0;
6839
6840     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6841         stats.hint = cpstats->move_name;
6842         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6843         stats.an_move_count = cpstats->nr_moves;
6844     }
6845
6846     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
6847
6848     SetProgramStats( &stats );
6849 }
6850
6851 void
6852 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6853 {       // count all piece types
6854         int p, f, r;
6855         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6856         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6857         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6858                 p = board[r][f];
6859                 pCnt[p]++;
6860                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6861                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6862                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6863                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6864                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6865                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6866         }
6867 }
6868
6869 int
6870 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6871 {
6872         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6873         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6874
6875         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6876         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6877         if(myPawns == 2 && nMine == 3) // KPP
6878             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6879         if(myPawns == 1 && nMine == 2) // KP
6880             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6881         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6882             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6883         if(myPawns) return FALSE;
6884         if(pCnt[WhiteRook+side])
6885             return pCnt[BlackRook-side] ||
6886                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6887                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6888                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6889         if(pCnt[WhiteCannon+side]) {
6890             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6891             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6892         }
6893         if(pCnt[WhiteKnight+side])
6894             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6895         return FALSE;
6896 }
6897
6898 int
6899 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6900 {
6901         VariantClass v = gameInfo.variant;
6902
6903         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6904         if(v == VariantShatranj) return TRUE; // always winnable through baring
6905         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6906         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6907
6908         if(v == VariantXiangqi) {
6909                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6910
6911                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6912                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6913                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6914                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6915                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6916                 if(stale) // we have at least one last-rank P plus perhaps C
6917                     return majors // KPKX
6918                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6919                 else // KCA*E*
6920                     return pCnt[WhiteFerz+side] // KCAK
6921                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6922                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6923                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6924
6925         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6926                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6927
6928                 if(nMine == 1) return FALSE; // bare King
6929                 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
6930                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6931                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6932                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6933                 if(pCnt[WhiteKnight+side])
6934                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6935                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6936                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6937                 if(nBishops)
6938                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6939                 if(pCnt[WhiteAlfil+side])
6940                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6941                 if(pCnt[WhiteWazir+side])
6942                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6943         }
6944
6945         return TRUE;
6946 }
6947
6948 int
6949 Adjudicate(ChessProgramState *cps)
6950 {       // [HGM] some adjudications useful with buggy engines
6951         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6952         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6953         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6954         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6955         int k, count = 0; static int bare = 1;
6956         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6957         Boolean canAdjudicate = !appData.icsActive;
6958
6959         // most tests only when we understand the game, i.e. legality-checking on
6960             if( appData.testLegality )
6961             {   /* [HGM] Some more adjudications for obstinate engines */
6962                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6963                 static int moveCount = 6;
6964                 ChessMove result;
6965                 char *reason = NULL;
6966
6967                 /* Count what is on board. */
6968                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6969
6970                 /* Some material-based adjudications that have to be made before stalemate test */
6971                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6972                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6973                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6974                      if(canAdjudicate && appData.checkMates) {
6975                          if(engineOpponent)
6976                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6977                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6978                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6979                          return 1;
6980                      }
6981                 }
6982
6983                 /* Bare King in Shatranj (loses) or Losers (wins) */
6984                 if( nrW == 1 || nrB == 1) {
6985                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6986                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6987                      if(canAdjudicate && appData.checkMates) {
6988                          if(engineOpponent)
6989                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6990                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6991                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6992                          return 1;
6993                      }
6994                   } else
6995                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6996                   {    /* bare King */
6997                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6998                         if(canAdjudicate && appData.checkMates) {
6999                             /* but only adjudicate if adjudication enabled */
7000                             if(engineOpponent)
7001                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7002                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7003                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7004                             return 1;
7005                         }
7006                   }
7007                 } else bare = 1;
7008
7009
7010             // don't wait for engine to announce game end if we can judge ourselves
7011             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7012               case MT_CHECK:
7013                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7014                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7015                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7016                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7017                             checkCnt++;
7018                         if(checkCnt >= 2) {
7019                             reason = "Xboard adjudication: 3rd check";
7020                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7021                             break;
7022                         }
7023                     }
7024                 }
7025               case MT_NONE:
7026               default:
7027                 break;
7028               case MT_STALEMATE:
7029               case MT_STAINMATE:
7030                 reason = "Xboard adjudication: Stalemate";
7031                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7032                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7033                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7034                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7035                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7036                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7037                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7038                                                                         EP_CHECKMATE : EP_WINS);
7039                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7040                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7041                 }
7042                 break;
7043               case MT_CHECKMATE:
7044                 reason = "Xboard adjudication: Checkmate";
7045                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7046                 break;
7047             }
7048
7049                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7050                     case EP_STALEMATE:
7051                         result = GameIsDrawn; break;
7052                     case EP_CHECKMATE:
7053                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7054                     case EP_WINS:
7055                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7056                     default:
7057                         result = EndOfFile;
7058                 }
7059                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7060                     if(engineOpponent)
7061                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7062                     GameEnds( result, reason, GE_XBOARD );
7063                     return 1;
7064                 }
7065
7066                 /* Next absolutely insufficient mating material. */
7067                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7068                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7069                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7070
7071                      /* always flag draws, for judging claims */
7072                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7073
7074                      if(canAdjudicate && appData.materialDraws) {
7075                          /* but only adjudicate them if adjudication enabled */
7076                          if(engineOpponent) {
7077                            SendToProgram("force\n", engineOpponent); // suppress reply
7078                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7079                          }
7080                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7081                          return 1;
7082                      }
7083                 }
7084
7085                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7086                 if(gameInfo.variant == VariantXiangqi ?
7087                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7088                  : nrW + nrB == 4 &&
7089                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7090                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7091                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7092                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7093                    ) ) {
7094                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7095                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7096                           if(engineOpponent) {
7097                             SendToProgram("force\n", engineOpponent); // suppress reply
7098                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7099                           }
7100                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7101                           return 1;
7102                      }
7103                 } else moveCount = 6;
7104             }
7105         if (appData.debugMode) { int i;
7106             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7107                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7108                     appData.drawRepeats);
7109             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7110               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7111
7112         }
7113
7114         // Repetition draws and 50-move rule can be applied independently of legality testing
7115
7116                 /* Check for rep-draws */
7117                 count = 0;
7118                 for(k = forwardMostMove-2;
7119                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7120                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7121                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7122                     k-=2)
7123                 {   int rights=0;
7124                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7125                         /* compare castling rights */
7126                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7127                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7128                                 rights++; /* King lost rights, while rook still had them */
7129                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7130                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7131                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7132                                    rights++; /* but at least one rook lost them */
7133                         }
7134                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7135                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7136                                 rights++;
7137                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7138                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7139                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7140                                    rights++;
7141                         }
7142                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7143                             && appData.drawRepeats > 1) {
7144                              /* adjudicate after user-specified nr of repeats */
7145                              int result = GameIsDrawn;
7146                              char *details = "XBoard adjudication: repetition draw";
7147                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7148                                 // [HGM] xiangqi: check for forbidden perpetuals
7149                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7150                                 for(m=forwardMostMove; m>k; m-=2) {
7151                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7152                                         ourPerpetual = 0; // the current mover did not always check
7153                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7154                                         hisPerpetual = 0; // the opponent did not always check
7155                                 }
7156                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7157                                                                         ourPerpetual, hisPerpetual);
7158                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7159                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7160                                     details = "Xboard adjudication: perpetual checking";
7161                                 } else
7162                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7163                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7164                                 } else
7165                                 // Now check for perpetual chases
7166                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7167                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7168                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7169                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7170                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7171                                         details = "Xboard adjudication: perpetual chasing";
7172                                     } else
7173                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7174                                         break; // Abort repetition-checking loop.
7175                                 }
7176                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7177                              }
7178                              if(engineOpponent) {
7179                                SendToProgram("force\n", engineOpponent); // suppress reply
7180                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7181                              }
7182                              GameEnds( result, details, GE_XBOARD );
7183                              return 1;
7184                         }
7185                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7186                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7187                     }
7188                 }
7189
7190                 /* Now we test for 50-move draws. Determine ply count */
7191                 count = forwardMostMove;
7192                 /* look for last irreversble move */
7193                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7194                     count--;
7195                 /* if we hit starting position, add initial plies */
7196                 if( count == backwardMostMove )
7197                     count -= initialRulePlies;
7198                 count = forwardMostMove - count;
7199                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7200                         // adjust reversible move counter for checks in Xiangqi
7201                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7202                         if(i < backwardMostMove) i = backwardMostMove;
7203                         while(i <= forwardMostMove) {
7204                                 lastCheck = inCheck; // check evasion does not count
7205                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7206                                 if(inCheck || lastCheck) count--; // check does not count
7207                                 i++;
7208                         }
7209                 }
7210                 if( count >= 100)
7211                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7212                          /* this is used to judge if draw claims are legal */
7213                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
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, "Xboard adjudication: 50-move rule", GE_XBOARD );
7219                          return 1;
7220                 }
7221
7222                 /* if draw offer is pending, treat it as a draw claim
7223                  * when draw condition present, to allow engines a way to
7224                  * claim draws before making their move to avoid a race
7225                  * condition occurring after their move
7226                  */
7227                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7228                          char *p = NULL;
7229                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7230                              p = "Draw claim: 50-move rule";
7231                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7232                              p = "Draw claim: 3-fold repetition";
7233                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7234                              p = "Draw claim: insufficient mating material";
7235                          if( p != NULL && canAdjudicate) {
7236                              if(engineOpponent) {
7237                                SendToProgram("force\n", engineOpponent); // suppress reply
7238                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7239                              }
7240                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7241                              return 1;
7242                          }
7243                 }
7244
7245                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7246                     if(engineOpponent) {
7247                       SendToProgram("force\n", engineOpponent); // suppress reply
7248                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7249                     }
7250                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7251                     return 1;
7252                 }
7253         return 0;
7254 }
7255
7256 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7257 {   // [HGM] book: this routine intercepts moves to simulate book replies
7258     char *bookHit = NULL;
7259
7260     //first determine if the incoming move brings opponent into his book
7261     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7262         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7263     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7264     if(bookHit != NULL && !cps->bookSuspend) {
7265         // make sure opponent is not going to reply after receiving move to book position
7266         SendToProgram("force\n", cps);
7267         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7268     }
7269     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7270     // now arrange restart after book miss
7271     if(bookHit) {
7272         // after a book hit we never send 'go', and the code after the call to this routine
7273         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7274         char buf[MSG_SIZ];
7275         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7276         SendToProgram(buf, cps);
7277         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7278     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7279         SendToProgram("go\n", cps);
7280         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7281     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7282         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7283             SendToProgram("go\n", cps);
7284         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7285     }
7286     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7287 }
7288
7289 char *savedMessage;
7290 ChessProgramState *savedState;
7291 void DeferredBookMove(void)
7292 {
7293         if(savedState->lastPing != savedState->lastPong)
7294                     ScheduleDelayedEvent(DeferredBookMove, 10);
7295         else
7296         HandleMachineMove(savedMessage, savedState);
7297 }
7298
7299 void
7300 HandleMachineMove(message, cps)
7301      char *message;
7302      ChessProgramState *cps;
7303 {
7304     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7305     char realname[MSG_SIZ];
7306     int fromX, fromY, toX, toY;
7307     ChessMove moveType;
7308     char promoChar;
7309     char *p;
7310     int machineWhite;
7311     char *bookHit;
7312
7313     cps->userError = 0;
7314
7315 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7316     /*
7317      * Kludge to ignore BEL characters
7318      */
7319     while (*message == '\007') message++;
7320
7321     /*
7322      * [HGM] engine debug message: ignore lines starting with '#' character
7323      */
7324     if(cps->debug && *message == '#') return;
7325
7326     /*
7327      * Look for book output
7328      */
7329     if (cps == &first && bookRequested) {
7330         if (message[0] == '\t' || message[0] == ' ') {
7331             /* Part of the book output is here; append it */
7332             strcat(bookOutput, message);
7333             strcat(bookOutput, "  \n");
7334             return;
7335         } else if (bookOutput[0] != NULLCHAR) {
7336             /* All of book output has arrived; display it */
7337             char *p = bookOutput;
7338             while (*p != NULLCHAR) {
7339                 if (*p == '\t') *p = ' ';
7340                 p++;
7341             }
7342             DisplayInformation(bookOutput);
7343             bookRequested = FALSE;
7344             /* Fall through to parse the current output */
7345         }
7346     }
7347
7348     /*
7349      * Look for machine move.
7350      */
7351     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7352         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7353     {
7354         /* This method is only useful on engines that support ping */
7355         if (cps->lastPing != cps->lastPong) {
7356           if (gameMode == BeginningOfGame) {
7357             /* Extra move from before last new; ignore */
7358             if (appData.debugMode) {
7359                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7360             }
7361           } else {
7362             if (appData.debugMode) {
7363                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7364                         cps->which, gameMode);
7365             }
7366
7367             SendToProgram("undo\n", cps);
7368           }
7369           return;
7370         }
7371
7372         switch (gameMode) {
7373           case BeginningOfGame:
7374             /* Extra move from before last reset; ignore */
7375             if (appData.debugMode) {
7376                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7377             }
7378             return;
7379
7380           case EndOfGame:
7381           case IcsIdle:
7382           default:
7383             /* Extra move after we tried to stop.  The mode test is
7384                not a reliable way of detecting this problem, but it's
7385                the best we can do on engines that don't support ping.
7386             */
7387             if (appData.debugMode) {
7388                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7389                         cps->which, gameMode);
7390             }
7391             SendToProgram("undo\n", cps);
7392             return;
7393
7394           case MachinePlaysWhite:
7395           case IcsPlayingWhite:
7396             machineWhite = TRUE;
7397             break;
7398
7399           case MachinePlaysBlack:
7400           case IcsPlayingBlack:
7401             machineWhite = FALSE;
7402             break;
7403
7404           case TwoMachinesPlay:
7405             machineWhite = (cps->twoMachinesColor[0] == 'w');
7406             break;
7407         }
7408         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7409             if (appData.debugMode) {
7410                 fprintf(debugFP,
7411                         "Ignoring move out of turn by %s, gameMode %d"
7412                         ", forwardMost %d\n",
7413                         cps->which, gameMode, forwardMostMove);
7414             }
7415             return;
7416         }
7417
7418     if (appData.debugMode) { int f = forwardMostMove;
7419         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7420                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7421                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7422     }
7423         if(cps->alphaRank) AlphaRank(machineMove, 4);
7424         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7425                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7426             /* Machine move could not be parsed; ignore it. */
7427           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7428                     machineMove, _(cps->which));
7429             DisplayError(buf1, 0);
7430             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7431                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7432             if (gameMode == TwoMachinesPlay) {
7433               GameEnds(machineWhite ? BlackWins : WhiteWins,
7434                        buf1, GE_XBOARD);
7435             }
7436             return;
7437         }
7438
7439         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7440         /* So we have to redo legality test with true e.p. status here,  */
7441         /* to make sure an illegal e.p. capture does not slip through,   */
7442         /* to cause a forfeit on a justified illegal-move complaint      */
7443         /* of the opponent.                                              */
7444         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7445            ChessMove moveType;
7446            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7447                              fromY, fromX, toY, toX, promoChar);
7448             if (appData.debugMode) {
7449                 int i;
7450                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7451                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7452                 fprintf(debugFP, "castling rights\n");
7453             }
7454             if(moveType == IllegalMove) {
7455               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7456                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7457                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7458                            buf1, GE_XBOARD);
7459                 return;
7460            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7461            /* [HGM] Kludge to handle engines that send FRC-style castling
7462               when they shouldn't (like TSCP-Gothic) */
7463            switch(moveType) {
7464              case WhiteASideCastleFR:
7465              case BlackASideCastleFR:
7466                toX+=2;
7467                currentMoveString[2]++;
7468                break;
7469              case WhiteHSideCastleFR:
7470              case BlackHSideCastleFR:
7471                toX--;
7472                currentMoveString[2]--;
7473                break;
7474              default: ; // nothing to do, but suppresses warning of pedantic compilers
7475            }
7476         }
7477         hintRequested = FALSE;
7478         lastHint[0] = NULLCHAR;
7479         bookRequested = FALSE;
7480         /* Program may be pondering now */
7481         cps->maybeThinking = TRUE;
7482         if (cps->sendTime == 2) cps->sendTime = 1;
7483         if (cps->offeredDraw) cps->offeredDraw--;
7484
7485         /* [AS] Save move info*/
7486         pvInfoList[ forwardMostMove ].score = programStats.score;
7487         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7488         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7489
7490         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7491
7492         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7493         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7494             int count = 0;
7495
7496             while( count < adjudicateLossPlies ) {
7497                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7498
7499                 if( count & 1 ) {
7500                     score = -score; /* Flip score for winning side */
7501                 }
7502
7503                 if( score > adjudicateLossThreshold ) {
7504                     break;
7505                 }
7506
7507                 count++;
7508             }
7509
7510             if( count >= adjudicateLossPlies ) {
7511                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7512
7513                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7514                     "Xboard adjudication",
7515                     GE_XBOARD );
7516
7517                 return;
7518             }
7519         }
7520
7521         if(Adjudicate(cps)) {
7522             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7523             return; // [HGM] adjudicate: for all automatic game ends
7524         }
7525
7526 #if ZIPPY
7527         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7528             first.initDone) {
7529           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7530                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7531                 SendToICS("draw ");
7532                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7533           }
7534           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7535           ics_user_moved = 1;
7536           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7537                 char buf[3*MSG_SIZ];
7538
7539                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7540                         programStats.score / 100.,
7541                         programStats.depth,
7542                         programStats.time / 100.,
7543                         (unsigned int)programStats.nodes,
7544                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7545                         programStats.movelist);
7546                 SendToICS(buf);
7547 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7548           }
7549         }
7550 #endif
7551
7552         /* [AS] Clear stats for next move */
7553         ClearProgramStats();
7554         thinkOutput[0] = NULLCHAR;
7555         hiddenThinkOutputState = 0;
7556
7557         bookHit = NULL;
7558         if (gameMode == TwoMachinesPlay) {
7559             /* [HGM] relaying draw offers moved to after reception of move */
7560             /* and interpreting offer as claim if it brings draw condition */
7561             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7562                 SendToProgram("draw\n", cps->other);
7563             }
7564             if (cps->other->sendTime) {
7565                 SendTimeRemaining(cps->other,
7566                                   cps->other->twoMachinesColor[0] == 'w');
7567             }
7568             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7569             if (firstMove && !bookHit) {
7570                 firstMove = FALSE;
7571                 if (cps->other->useColors) {
7572                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7573                 }
7574                 SendToProgram("go\n", cps->other);
7575             }
7576             cps->other->maybeThinking = TRUE;
7577         }
7578
7579         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7580
7581         if (!pausing && appData.ringBellAfterMoves) {
7582             RingBell();
7583         }
7584
7585         /*
7586          * Reenable menu items that were disabled while
7587          * machine was thinking
7588          */
7589         if (gameMode != TwoMachinesPlay)
7590             SetUserThinkingEnables();
7591
7592         // [HGM] book: after book hit opponent has received move and is now in force mode
7593         // force the book reply into it, and then fake that it outputted this move by jumping
7594         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7595         if(bookHit) {
7596                 static char bookMove[MSG_SIZ]; // a bit generous?
7597
7598                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7599                 strcat(bookMove, bookHit);
7600                 message = bookMove;
7601                 cps = cps->other;
7602                 programStats.nodes = programStats.depth = programStats.time =
7603                 programStats.score = programStats.got_only_move = 0;
7604                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7605
7606                 if(cps->lastPing != cps->lastPong) {
7607                     savedMessage = message; // args for deferred call
7608                     savedState = cps;
7609                     ScheduleDelayedEvent(DeferredBookMove, 10);
7610                     return;
7611                 }
7612                 goto FakeBookMove;
7613         }
7614
7615         return;
7616     }
7617
7618     /* Set special modes for chess engines.  Later something general
7619      *  could be added here; for now there is just one kludge feature,
7620      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7621      *  when "xboard" is given as an interactive command.
7622      */
7623     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7624         cps->useSigint = FALSE;
7625         cps->useSigterm = FALSE;
7626     }
7627     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7628       ParseFeatures(message+8, cps);
7629       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7630     }
7631
7632     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7633       int dummy, s=6; char buf[MSG_SIZ];
7634       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7635       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7636       ParseFEN(boards[0], &dummy, message+s);
7637       DrawPosition(TRUE, boards[0]);
7638       startedFromSetupPosition = TRUE;
7639       return;
7640     }
7641     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7642      * want this, I was asked to put it in, and obliged.
7643      */
7644     if (!strncmp(message, "setboard ", 9)) {
7645         Board initial_position;
7646
7647         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7648
7649         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7650             DisplayError(_("Bad FEN received from engine"), 0);
7651             return ;
7652         } else {
7653            Reset(TRUE, FALSE);
7654            CopyBoard(boards[0], initial_position);
7655            initialRulePlies = FENrulePlies;
7656            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7657            else gameMode = MachinePlaysBlack;
7658            DrawPosition(FALSE, boards[currentMove]);
7659         }
7660         return;
7661     }
7662
7663     /*
7664      * Look for communication commands
7665      */
7666     if (!strncmp(message, "telluser ", 9)) {
7667         if(message[9] == '\\' && message[10] == '\\')
7668             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7669         DisplayNote(message + 9);
7670         return;
7671     }
7672     if (!strncmp(message, "tellusererror ", 14)) {
7673         cps->userError = 1;
7674         if(message[14] == '\\' && message[15] == '\\')
7675             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7676         DisplayError(message + 14, 0);
7677         return;
7678     }
7679     if (!strncmp(message, "tellopponent ", 13)) {
7680       if (appData.icsActive) {
7681         if (loggedOn) {
7682           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7683           SendToICS(buf1);
7684         }
7685       } else {
7686         DisplayNote(message + 13);
7687       }
7688       return;
7689     }
7690     if (!strncmp(message, "tellothers ", 11)) {
7691       if (appData.icsActive) {
7692         if (loggedOn) {
7693           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7694           SendToICS(buf1);
7695         }
7696       }
7697       return;
7698     }
7699     if (!strncmp(message, "tellall ", 8)) {
7700       if (appData.icsActive) {
7701         if (loggedOn) {
7702           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7703           SendToICS(buf1);
7704         }
7705       } else {
7706         DisplayNote(message + 8);
7707       }
7708       return;
7709     }
7710     if (strncmp(message, "warning", 7) == 0) {
7711         /* Undocumented feature, use tellusererror in new code */
7712         DisplayError(message, 0);
7713         return;
7714     }
7715     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7716         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7717         strcat(realname, " query");
7718         AskQuestion(realname, buf2, buf1, cps->pr);
7719         return;
7720     }
7721     /* Commands from the engine directly to ICS.  We don't allow these to be
7722      *  sent until we are logged on. Crafty kibitzes have been known to
7723      *  interfere with the login process.
7724      */
7725     if (loggedOn) {
7726         if (!strncmp(message, "tellics ", 8)) {
7727             SendToICS(message + 8);
7728             SendToICS("\n");
7729             return;
7730         }
7731         if (!strncmp(message, "tellicsnoalias ", 15)) {
7732             SendToICS(ics_prefix);
7733             SendToICS(message + 15);
7734             SendToICS("\n");
7735             return;
7736         }
7737         /* The following are for backward compatibility only */
7738         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7739             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7740             SendToICS(ics_prefix);
7741             SendToICS(message);
7742             SendToICS("\n");
7743             return;
7744         }
7745     }
7746     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7747         return;
7748     }
7749     /*
7750      * If the move is illegal, cancel it and redraw the board.
7751      * Also deal with other error cases.  Matching is rather loose
7752      * here to accommodate engines written before the spec.
7753      */
7754     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7755         strncmp(message, "Error", 5) == 0) {
7756         if (StrStr(message, "name") ||
7757             StrStr(message, "rating") || StrStr(message, "?") ||
7758             StrStr(message, "result") || StrStr(message, "board") ||
7759             StrStr(message, "bk") || StrStr(message, "computer") ||
7760             StrStr(message, "variant") || StrStr(message, "hint") ||
7761             StrStr(message, "random") || StrStr(message, "depth") ||
7762             StrStr(message, "accepted")) {
7763             return;
7764         }
7765         if (StrStr(message, "protover")) {
7766           /* Program is responding to input, so it's apparently done
7767              initializing, and this error message indicates it is
7768              protocol version 1.  So we don't need to wait any longer
7769              for it to initialize and send feature commands. */
7770           FeatureDone(cps, 1);
7771           cps->protocolVersion = 1;
7772           return;
7773         }
7774         cps->maybeThinking = FALSE;
7775
7776         if (StrStr(message, "draw")) {
7777             /* Program doesn't have "draw" command */
7778             cps->sendDrawOffers = 0;
7779             return;
7780         }
7781         if (cps->sendTime != 1 &&
7782             (StrStr(message, "time") || StrStr(message, "otim"))) {
7783           /* Program apparently doesn't have "time" or "otim" command */
7784           cps->sendTime = 0;
7785           return;
7786         }
7787         if (StrStr(message, "analyze")) {
7788             cps->analysisSupport = FALSE;
7789             cps->analyzing = FALSE;
7790             Reset(FALSE, TRUE);
7791             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7792             DisplayError(buf2, 0);
7793             return;
7794         }
7795         if (StrStr(message, "(no matching move)st")) {
7796           /* Special kludge for GNU Chess 4 only */
7797           cps->stKludge = TRUE;
7798           SendTimeControl(cps, movesPerSession, timeControl,
7799                           timeIncrement, appData.searchDepth,
7800                           searchTime);
7801           return;
7802         }
7803         if (StrStr(message, "(no matching move)sd")) {
7804           /* Special kludge for GNU Chess 4 only */
7805           cps->sdKludge = TRUE;
7806           SendTimeControl(cps, movesPerSession, timeControl,
7807                           timeIncrement, appData.searchDepth,
7808                           searchTime);
7809           return;
7810         }
7811         if (!StrStr(message, "llegal")) {
7812             return;
7813         }
7814         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7815             gameMode == IcsIdle) return;
7816         if (forwardMostMove <= backwardMostMove) return;
7817         if (pausing) PauseEvent();
7818       if(appData.forceIllegal) {
7819             // [HGM] illegal: machine refused move; force position after move into it
7820           SendToProgram("force\n", cps);
7821           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7822                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7823                 // when black is to move, while there might be nothing on a2 or black
7824                 // might already have the move. So send the board as if white has the move.
7825                 // But first we must change the stm of the engine, as it refused the last move
7826                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7827                 if(WhiteOnMove(forwardMostMove)) {
7828                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7829                     SendBoard(cps, forwardMostMove); // kludgeless board
7830                 } else {
7831                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7832                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7833                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7834                 }
7835           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7836             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7837                  gameMode == TwoMachinesPlay)
7838               SendToProgram("go\n", cps);
7839             return;
7840       } else
7841         if (gameMode == PlayFromGameFile) {
7842             /* Stop reading this game file */
7843             gameMode = EditGame;
7844             ModeHighlight();
7845         }
7846         /* [HGM] illegal-move claim should forfeit game when Xboard */
7847         /* only passes fully legal moves                            */
7848         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7849             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7850                                 "False illegal-move claim", GE_XBOARD );
7851             return; // do not take back move we tested as valid
7852         }
7853         currentMove = forwardMostMove-1;
7854         DisplayMove(currentMove-1); /* before DisplayMoveError */
7855         SwitchClocks(forwardMostMove-1); // [HGM] race
7856         DisplayBothClocks();
7857         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7858                 parseList[currentMove], _(cps->which));
7859         DisplayMoveError(buf1);
7860         DrawPosition(FALSE, boards[currentMove]);
7861         return;
7862     }
7863     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7864         /* Program has a broken "time" command that
7865            outputs a string not ending in newline.
7866            Don't use it. */
7867         cps->sendTime = 0;
7868     }
7869
7870     /*
7871      * If chess program startup fails, exit with an error message.
7872      * Attempts to recover here are futile.
7873      */
7874     if ((StrStr(message, "unknown host") != NULL)
7875         || (StrStr(message, "No remote directory") != NULL)
7876         || (StrStr(message, "not found") != NULL)
7877         || (StrStr(message, "No such file") != NULL)
7878         || (StrStr(message, "can't alloc") != NULL)
7879         || (StrStr(message, "Permission denied") != NULL)) {
7880
7881         cps->maybeThinking = FALSE;
7882         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7883                 _(cps->which), cps->program, cps->host, message);
7884         RemoveInputSource(cps->isr);
7885         DisplayFatalError(buf1, 0, 1);
7886         return;
7887     }
7888
7889     /*
7890      * Look for hint output
7891      */
7892     if (sscanf(message, "Hint: %s", buf1) == 1) {
7893         if (cps == &first && hintRequested) {
7894             hintRequested = FALSE;
7895             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7896                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7897                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7898                                     PosFlags(forwardMostMove),
7899                                     fromY, fromX, toY, toX, promoChar, buf1);
7900                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7901                 DisplayInformation(buf2);
7902             } else {
7903                 /* Hint move could not be parsed!? */
7904               snprintf(buf2, sizeof(buf2),
7905                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7906                         buf1, _(cps->which));
7907                 DisplayError(buf2, 0);
7908             }
7909         } else {
7910           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7911         }
7912         return;
7913     }
7914
7915     /*
7916      * Ignore other messages if game is not in progress
7917      */
7918     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7919         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7920
7921     /*
7922      * look for win, lose, draw, or draw offer
7923      */
7924     if (strncmp(message, "1-0", 3) == 0) {
7925         char *p, *q, *r = "";
7926         p = strchr(message, '{');
7927         if (p) {
7928             q = strchr(p, '}');
7929             if (q) {
7930                 *q = NULLCHAR;
7931                 r = p + 1;
7932             }
7933         }
7934         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7935         return;
7936     } else if (strncmp(message, "0-1", 3) == 0) {
7937         char *p, *q, *r = "";
7938         p = strchr(message, '{');
7939         if (p) {
7940             q = strchr(p, '}');
7941             if (q) {
7942                 *q = NULLCHAR;
7943                 r = p + 1;
7944             }
7945         }
7946         /* Kludge for Arasan 4.1 bug */
7947         if (strcmp(r, "Black resigns") == 0) {
7948             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7949             return;
7950         }
7951         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7952         return;
7953     } else if (strncmp(message, "1/2", 3) == 0) {
7954         char *p, *q, *r = "";
7955         p = strchr(message, '{');
7956         if (p) {
7957             q = strchr(p, '}');
7958             if (q) {
7959                 *q = NULLCHAR;
7960                 r = p + 1;
7961             }
7962         }
7963
7964         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7965         return;
7966
7967     } else if (strncmp(message, "White resign", 12) == 0) {
7968         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7969         return;
7970     } else if (strncmp(message, "Black resign", 12) == 0) {
7971         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7972         return;
7973     } else if (strncmp(message, "White matches", 13) == 0 ||
7974                strncmp(message, "Black matches", 13) == 0   ) {
7975         /* [HGM] ignore GNUShogi noises */
7976         return;
7977     } else if (strncmp(message, "White", 5) == 0 &&
7978                message[5] != '(' &&
7979                StrStr(message, "Black") == NULL) {
7980         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7981         return;
7982     } else if (strncmp(message, "Black", 5) == 0 &&
7983                message[5] != '(') {
7984         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7985         return;
7986     } else if (strcmp(message, "resign") == 0 ||
7987                strcmp(message, "computer resigns") == 0) {
7988         switch (gameMode) {
7989           case MachinePlaysBlack:
7990           case IcsPlayingBlack:
7991             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7992             break;
7993           case MachinePlaysWhite:
7994           case IcsPlayingWhite:
7995             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7996             break;
7997           case TwoMachinesPlay:
7998             if (cps->twoMachinesColor[0] == 'w')
7999               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8000             else
8001               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8002             break;
8003           default:
8004             /* can't happen */
8005             break;
8006         }
8007         return;
8008     } else if (strncmp(message, "opponent mates", 14) == 0) {
8009         switch (gameMode) {
8010           case MachinePlaysBlack:
8011           case IcsPlayingBlack:
8012             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8013             break;
8014           case MachinePlaysWhite:
8015           case IcsPlayingWhite:
8016             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8017             break;
8018           case TwoMachinesPlay:
8019             if (cps->twoMachinesColor[0] == 'w')
8020               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8021             else
8022               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8023             break;
8024           default:
8025             /* can't happen */
8026             break;
8027         }
8028         return;
8029     } else if (strncmp(message, "computer mates", 14) == 0) {
8030         switch (gameMode) {
8031           case MachinePlaysBlack:
8032           case IcsPlayingBlack:
8033             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8034             break;
8035           case MachinePlaysWhite:
8036           case IcsPlayingWhite:
8037             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8038             break;
8039           case TwoMachinesPlay:
8040             if (cps->twoMachinesColor[0] == 'w')
8041               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8042             else
8043               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8044             break;
8045           default:
8046             /* can't happen */
8047             break;
8048         }
8049         return;
8050     } else if (strncmp(message, "checkmate", 9) == 0) {
8051         if (WhiteOnMove(forwardMostMove)) {
8052             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8053         } else {
8054             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8055         }
8056         return;
8057     } else if (strstr(message, "Draw") != NULL ||
8058                strstr(message, "game is a draw") != NULL) {
8059         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8060         return;
8061     } else if (strstr(message, "offer") != NULL &&
8062                strstr(message, "draw") != NULL) {
8063 #if ZIPPY
8064         if (appData.zippyPlay && first.initDone) {
8065             /* Relay offer to ICS */
8066             SendToICS(ics_prefix);
8067             SendToICS("draw\n");
8068         }
8069 #endif
8070         cps->offeredDraw = 2; /* valid until this engine moves twice */
8071         if (gameMode == TwoMachinesPlay) {
8072             if (cps->other->offeredDraw) {
8073                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8074             /* [HGM] in two-machine mode we delay relaying draw offer      */
8075             /* until after we also have move, to see if it is really claim */
8076             }
8077         } else if (gameMode == MachinePlaysWhite ||
8078                    gameMode == MachinePlaysBlack) {
8079           if (userOfferedDraw) {
8080             DisplayInformation(_("Machine accepts your draw offer"));
8081             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8082           } else {
8083             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8084           }
8085         }
8086     }
8087
8088
8089     /*
8090      * Look for thinking output
8091      */
8092     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8093           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8094                                 ) {
8095         int plylev, mvleft, mvtot, curscore, time;
8096         char mvname[MOVE_LEN];
8097         u64 nodes; // [DM]
8098         char plyext;
8099         int ignore = FALSE;
8100         int prefixHint = FALSE;
8101         mvname[0] = NULLCHAR;
8102
8103         switch (gameMode) {
8104           case MachinePlaysBlack:
8105           case IcsPlayingBlack:
8106             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8107             break;
8108           case MachinePlaysWhite:
8109           case IcsPlayingWhite:
8110             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8111             break;
8112           case AnalyzeMode:
8113           case AnalyzeFile:
8114             break;
8115           case IcsObserving: /* [DM] icsEngineAnalyze */
8116             if (!appData.icsEngineAnalyze) ignore = TRUE;
8117             break;
8118           case TwoMachinesPlay:
8119             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8120                 ignore = TRUE;
8121             }
8122             break;
8123           default:
8124             ignore = TRUE;
8125             break;
8126         }
8127
8128         if (!ignore) {
8129             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8130             buf1[0] = NULLCHAR;
8131             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8132                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8133
8134                 if (plyext != ' ' && plyext != '\t') {
8135                     time *= 100;
8136                 }
8137
8138                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8139                 if( cps->scoreIsAbsolute &&
8140                     ( gameMode == MachinePlaysBlack ||
8141                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8142                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8143                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8144                      !WhiteOnMove(currentMove)
8145                     ) )
8146                 {
8147                     curscore = -curscore;
8148                 }
8149
8150
8151                 tempStats.depth = plylev;
8152                 tempStats.nodes = nodes;
8153                 tempStats.time = time;
8154                 tempStats.score = curscore;
8155                 tempStats.got_only_move = 0;
8156
8157                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8158                         int ticklen;
8159
8160                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8161                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8162                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8163                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8164                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8165                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8166                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8167                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8168                 }
8169
8170                 /* Buffer overflow protection */
8171                 if (buf1[0] != NULLCHAR) {
8172                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8173                         && appData.debugMode) {
8174                         fprintf(debugFP,
8175                                 "PV is too long; using the first %u bytes.\n",
8176                                 (unsigned) sizeof(tempStats.movelist) - 1);
8177                     }
8178
8179                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8180                 } else {
8181                     sprintf(tempStats.movelist, " no PV\n");
8182                 }
8183
8184                 if (tempStats.seen_stat) {
8185                     tempStats.ok_to_send = 1;
8186                 }
8187
8188                 if (strchr(tempStats.movelist, '(') != NULL) {
8189                     tempStats.line_is_book = 1;
8190                     tempStats.nr_moves = 0;
8191                     tempStats.moves_left = 0;
8192                 } else {
8193                     tempStats.line_is_book = 0;
8194                 }
8195
8196                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8197                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8198
8199                 SendProgramStatsToFrontend( cps, &tempStats );
8200
8201                 /*
8202                     [AS] Protect the thinkOutput buffer from overflow... this
8203                     is only useful if buf1 hasn't overflowed first!
8204                 */
8205                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8206                          plylev,
8207                          (gameMode == TwoMachinesPlay ?
8208                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8209                          ((double) curscore) / 100.0,
8210                          prefixHint ? lastHint : "",
8211                          prefixHint ? " " : "" );
8212
8213                 if( buf1[0] != NULLCHAR ) {
8214                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8215
8216                     if( strlen(buf1) > max_len ) {
8217                         if( appData.debugMode) {
8218                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8219                         }
8220                         buf1[max_len+1] = '\0';
8221                     }
8222
8223                     strcat( thinkOutput, buf1 );
8224                 }
8225
8226                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8227                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8228                     DisplayMove(currentMove - 1);
8229                 }
8230                 return;
8231
8232             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8233                 /* crafty (9.25+) says "(only move) <move>"
8234                  * if there is only 1 legal move
8235                  */
8236                 sscanf(p, "(only move) %s", buf1);
8237                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8238                 sprintf(programStats.movelist, "%s (only move)", buf1);
8239                 programStats.depth = 1;
8240                 programStats.nr_moves = 1;
8241                 programStats.moves_left = 1;
8242                 programStats.nodes = 1;
8243                 programStats.time = 1;
8244                 programStats.got_only_move = 1;
8245
8246                 /* Not really, but we also use this member to
8247                    mean "line isn't going to change" (Crafty
8248                    isn't searching, so stats won't change) */
8249                 programStats.line_is_book = 1;
8250
8251                 SendProgramStatsToFrontend( cps, &programStats );
8252
8253                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8254                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8255                     DisplayMove(currentMove - 1);
8256                 }
8257                 return;
8258             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8259                               &time, &nodes, &plylev, &mvleft,
8260                               &mvtot, mvname) >= 5) {
8261                 /* The stat01: line is from Crafty (9.29+) in response
8262                    to the "." command */
8263                 programStats.seen_stat = 1;
8264                 cps->maybeThinking = TRUE;
8265
8266                 if (programStats.got_only_move || !appData.periodicUpdates)
8267                   return;
8268
8269                 programStats.depth = plylev;
8270                 programStats.time = time;
8271                 programStats.nodes = nodes;
8272                 programStats.moves_left = mvleft;
8273                 programStats.nr_moves = mvtot;
8274                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8275                 programStats.ok_to_send = 1;
8276                 programStats.movelist[0] = '\0';
8277
8278                 SendProgramStatsToFrontend( cps, &programStats );
8279
8280                 return;
8281
8282             } else if (strncmp(message,"++",2) == 0) {
8283                 /* Crafty 9.29+ outputs this */
8284                 programStats.got_fail = 2;
8285                 return;
8286
8287             } else if (strncmp(message,"--",2) == 0) {
8288                 /* Crafty 9.29+ outputs this */
8289                 programStats.got_fail = 1;
8290                 return;
8291
8292             } else if (thinkOutput[0] != NULLCHAR &&
8293                        strncmp(message, "    ", 4) == 0) {
8294                 unsigned message_len;
8295
8296                 p = message;
8297                 while (*p && *p == ' ') p++;
8298
8299                 message_len = strlen( p );
8300
8301                 /* [AS] Avoid buffer overflow */
8302                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8303                     strcat(thinkOutput, " ");
8304                     strcat(thinkOutput, p);
8305                 }
8306
8307                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8308                     strcat(programStats.movelist, " ");
8309                     strcat(programStats.movelist, p);
8310                 }
8311
8312                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8313                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8314                     DisplayMove(currentMove - 1);
8315                 }
8316                 return;
8317             }
8318         }
8319         else {
8320             buf1[0] = NULLCHAR;
8321
8322             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8323                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8324             {
8325                 ChessProgramStats cpstats;
8326
8327                 if (plyext != ' ' && plyext != '\t') {
8328                     time *= 100;
8329                 }
8330
8331                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8332                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8333                     curscore = -curscore;
8334                 }
8335
8336                 cpstats.depth = plylev;
8337                 cpstats.nodes = nodes;
8338                 cpstats.time = time;
8339                 cpstats.score = curscore;
8340                 cpstats.got_only_move = 0;
8341                 cpstats.movelist[0] = '\0';
8342
8343                 if (buf1[0] != NULLCHAR) {
8344                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8345                 }
8346
8347                 cpstats.ok_to_send = 0;
8348                 cpstats.line_is_book = 0;
8349                 cpstats.nr_moves = 0;
8350                 cpstats.moves_left = 0;
8351
8352                 SendProgramStatsToFrontend( cps, &cpstats );
8353             }
8354         }
8355     }
8356 }
8357
8358
8359 /* Parse a game score from the character string "game", and
8360    record it as the history of the current game.  The game
8361    score is NOT assumed to start from the standard position.
8362    The display is not updated in any way.
8363    */
8364 void
8365 ParseGameHistory(game)
8366      char *game;
8367 {
8368     ChessMove moveType;
8369     int fromX, fromY, toX, toY, boardIndex;
8370     char promoChar;
8371     char *p, *q;
8372     char buf[MSG_SIZ];
8373
8374     if (appData.debugMode)
8375       fprintf(debugFP, "Parsing game history: %s\n", game);
8376
8377     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8378     gameInfo.site = StrSave(appData.icsHost);
8379     gameInfo.date = PGNDate();
8380     gameInfo.round = StrSave("-");
8381
8382     /* Parse out names of players */
8383     while (*game == ' ') game++;
8384     p = buf;
8385     while (*game != ' ') *p++ = *game++;
8386     *p = NULLCHAR;
8387     gameInfo.white = StrSave(buf);
8388     while (*game == ' ') game++;
8389     p = buf;
8390     while (*game != ' ' && *game != '\n') *p++ = *game++;
8391     *p = NULLCHAR;
8392     gameInfo.black = StrSave(buf);
8393
8394     /* Parse moves */
8395     boardIndex = blackPlaysFirst ? 1 : 0;
8396     yynewstr(game);
8397     for (;;) {
8398         yyboardindex = boardIndex;
8399         moveType = (ChessMove) Myylex();
8400         switch (moveType) {
8401           case IllegalMove:             /* maybe suicide chess, etc. */
8402   if (appData.debugMode) {
8403     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8404     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8405     setbuf(debugFP, NULL);
8406   }
8407           case WhitePromotion:
8408           case BlackPromotion:
8409           case WhiteNonPromotion:
8410           case BlackNonPromotion:
8411           case NormalMove:
8412           case WhiteCapturesEnPassant:
8413           case BlackCapturesEnPassant:
8414           case WhiteKingSideCastle:
8415           case WhiteQueenSideCastle:
8416           case BlackKingSideCastle:
8417           case BlackQueenSideCastle:
8418           case WhiteKingSideCastleWild:
8419           case WhiteQueenSideCastleWild:
8420           case BlackKingSideCastleWild:
8421           case BlackQueenSideCastleWild:
8422           /* PUSH Fabien */
8423           case WhiteHSideCastleFR:
8424           case WhiteASideCastleFR:
8425           case BlackHSideCastleFR:
8426           case BlackASideCastleFR:
8427           /* POP Fabien */
8428             fromX = currentMoveString[0] - AAA;
8429             fromY = currentMoveString[1] - ONE;
8430             toX = currentMoveString[2] - AAA;
8431             toY = currentMoveString[3] - ONE;
8432             promoChar = currentMoveString[4];
8433             break;
8434           case WhiteDrop:
8435           case BlackDrop:
8436             fromX = moveType == WhiteDrop ?
8437               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8438             (int) CharToPiece(ToLower(currentMoveString[0]));
8439             fromY = DROP_RANK;
8440             toX = currentMoveString[2] - AAA;
8441             toY = currentMoveString[3] - ONE;
8442             promoChar = NULLCHAR;
8443             break;
8444           case AmbiguousMove:
8445             /* bug? */
8446             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8447   if (appData.debugMode) {
8448     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8449     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8450     setbuf(debugFP, NULL);
8451   }
8452             DisplayError(buf, 0);
8453             return;
8454           case ImpossibleMove:
8455             /* bug? */
8456             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8457   if (appData.debugMode) {
8458     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8459     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8460     setbuf(debugFP, NULL);
8461   }
8462             DisplayError(buf, 0);
8463             return;
8464           case EndOfFile:
8465             if (boardIndex < backwardMostMove) {
8466                 /* Oops, gap.  How did that happen? */
8467                 DisplayError(_("Gap in move list"), 0);
8468                 return;
8469             }
8470             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8471             if (boardIndex > forwardMostMove) {
8472                 forwardMostMove = boardIndex;
8473             }
8474             return;
8475           case ElapsedTime:
8476             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8477                 strcat(parseList[boardIndex-1], " ");
8478                 strcat(parseList[boardIndex-1], yy_text);
8479             }
8480             continue;
8481           case Comment:
8482           case PGNTag:
8483           case NAG:
8484           default:
8485             /* ignore */
8486             continue;
8487           case WhiteWins:
8488           case BlackWins:
8489           case GameIsDrawn:
8490           case GameUnfinished:
8491             if (gameMode == IcsExamining) {
8492                 if (boardIndex < backwardMostMove) {
8493                     /* Oops, gap.  How did that happen? */
8494                     return;
8495                 }
8496                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8497                 return;
8498             }
8499             gameInfo.result = moveType;
8500             p = strchr(yy_text, '{');
8501             if (p == NULL) p = strchr(yy_text, '(');
8502             if (p == NULL) {
8503                 p = yy_text;
8504                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8505             } else {
8506                 q = strchr(p, *p == '{' ? '}' : ')');
8507                 if (q != NULL) *q = NULLCHAR;
8508                 p++;
8509             }
8510             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8511             gameInfo.resultDetails = StrSave(p);
8512             continue;
8513         }
8514         if (boardIndex >= forwardMostMove &&
8515             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8516             backwardMostMove = blackPlaysFirst ? 1 : 0;
8517             return;
8518         }
8519         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8520                                  fromY, fromX, toY, toX, promoChar,
8521                                  parseList[boardIndex]);
8522         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8523         /* currentMoveString is set as a side-effect of yylex */
8524         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8525         strcat(moveList[boardIndex], "\n");
8526         boardIndex++;
8527         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8528         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8529           case MT_NONE:
8530           case MT_STALEMATE:
8531           default:
8532             break;
8533           case MT_CHECK:
8534             if(gameInfo.variant != VariantShogi)
8535                 strcat(parseList[boardIndex - 1], "+");
8536             break;
8537           case MT_CHECKMATE:
8538           case MT_STAINMATE:
8539             strcat(parseList[boardIndex - 1], "#");
8540             break;
8541         }
8542     }
8543 }
8544
8545
8546 /* Apply a move to the given board  */
8547 void
8548 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8549      int fromX, fromY, toX, toY;
8550      int promoChar;
8551      Board board;
8552 {
8553   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8554   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8555
8556     /* [HGM] compute & store e.p. status and castling rights for new position */
8557     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8558
8559       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8560       oldEP = (signed char)board[EP_STATUS];
8561       board[EP_STATUS] = EP_NONE;
8562
8563       if( board[toY][toX] != EmptySquare )
8564            board[EP_STATUS] = EP_CAPTURE;
8565
8566   if (fromY == DROP_RANK) {
8567         /* must be first */
8568         piece = board[toY][toX] = (ChessSquare) fromX;
8569   } else {
8570       int i;
8571
8572       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8573            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8574                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8575       } else
8576       if( board[fromY][fromX] == WhitePawn ) {
8577            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8578                board[EP_STATUS] = EP_PAWN_MOVE;
8579            if( toY-fromY==2) {
8580                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8581                         gameInfo.variant != VariantBerolina || toX < fromX)
8582                       board[EP_STATUS] = toX | berolina;
8583                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8584                         gameInfo.variant != VariantBerolina || toX > fromX)
8585                       board[EP_STATUS] = toX;
8586            }
8587       } else
8588       if( board[fromY][fromX] == BlackPawn ) {
8589            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8590                board[EP_STATUS] = EP_PAWN_MOVE;
8591            if( toY-fromY== -2) {
8592                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8593                         gameInfo.variant != VariantBerolina || toX < fromX)
8594                       board[EP_STATUS] = toX | berolina;
8595                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8596                         gameInfo.variant != VariantBerolina || toX > fromX)
8597                       board[EP_STATUS] = toX;
8598            }
8599        }
8600
8601        for(i=0; i<nrCastlingRights; i++) {
8602            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8603               board[CASTLING][i] == toX   && castlingRank[i] == toY
8604              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8605        }
8606
8607      if (fromX == toX && fromY == toY) return;
8608
8609      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8610      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8611      if(gameInfo.variant == VariantKnightmate)
8612          king += (int) WhiteUnicorn - (int) WhiteKing;
8613
8614     /* Code added by Tord: */
8615     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8616     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8617         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8618       board[fromY][fromX] = EmptySquare;
8619       board[toY][toX] = EmptySquare;
8620       if((toX > fromX) != (piece == WhiteRook)) {
8621         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8622       } else {
8623         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8624       }
8625     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8626                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8627       board[fromY][fromX] = EmptySquare;
8628       board[toY][toX] = EmptySquare;
8629       if((toX > fromX) != (piece == BlackRook)) {
8630         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8631       } else {
8632         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8633       }
8634     /* End of code added by Tord */
8635
8636     } else if (board[fromY][fromX] == king
8637         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8638         && toY == fromY && toX > fromX+1) {
8639         board[fromY][fromX] = EmptySquare;
8640         board[toY][toX] = king;
8641         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8642         board[fromY][BOARD_RGHT-1] = EmptySquare;
8643     } else if (board[fromY][fromX] == king
8644         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8645                && toY == fromY && toX < fromX-1) {
8646         board[fromY][fromX] = EmptySquare;
8647         board[toY][toX] = king;
8648         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8649         board[fromY][BOARD_LEFT] = EmptySquare;
8650     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8651                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8652                && toY >= BOARD_HEIGHT-promoRank
8653                ) {
8654         /* white pawn promotion */
8655         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8656         if (board[toY][toX] == EmptySquare) {
8657             board[toY][toX] = WhiteQueen;
8658         }
8659         if(gameInfo.variant==VariantBughouse ||
8660            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8661             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8662         board[fromY][fromX] = EmptySquare;
8663     } else if ((fromY == BOARD_HEIGHT-4)
8664                && (toX != fromX)
8665                && gameInfo.variant != VariantXiangqi
8666                && gameInfo.variant != VariantBerolina
8667                && (board[fromY][fromX] == WhitePawn)
8668                && (board[toY][toX] == EmptySquare)) {
8669         board[fromY][fromX] = EmptySquare;
8670         board[toY][toX] = WhitePawn;
8671         captured = board[toY - 1][toX];
8672         board[toY - 1][toX] = EmptySquare;
8673     } else if ((fromY == BOARD_HEIGHT-4)
8674                && (toX == fromX)
8675                && gameInfo.variant == VariantBerolina
8676                && (board[fromY][fromX] == WhitePawn)
8677                && (board[toY][toX] == EmptySquare)) {
8678         board[fromY][fromX] = EmptySquare;
8679         board[toY][toX] = WhitePawn;
8680         if(oldEP & EP_BEROLIN_A) {
8681                 captured = board[fromY][fromX-1];
8682                 board[fromY][fromX-1] = EmptySquare;
8683         }else{  captured = board[fromY][fromX+1];
8684                 board[fromY][fromX+1] = EmptySquare;
8685         }
8686     } else if (board[fromY][fromX] == king
8687         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8688                && toY == fromY && toX > fromX+1) {
8689         board[fromY][fromX] = EmptySquare;
8690         board[toY][toX] = king;
8691         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8692         board[fromY][BOARD_RGHT-1] = EmptySquare;
8693     } else if (board[fromY][fromX] == king
8694         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8695                && toY == fromY && toX < fromX-1) {
8696         board[fromY][fromX] = EmptySquare;
8697         board[toY][toX] = king;
8698         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8699         board[fromY][BOARD_LEFT] = EmptySquare;
8700     } else if (fromY == 7 && fromX == 3
8701                && board[fromY][fromX] == BlackKing
8702                && toY == 7 && toX == 5) {
8703         board[fromY][fromX] = EmptySquare;
8704         board[toY][toX] = BlackKing;
8705         board[fromY][7] = EmptySquare;
8706         board[toY][4] = BlackRook;
8707     } else if (fromY == 7 && fromX == 3
8708                && board[fromY][fromX] == BlackKing
8709                && toY == 7 && toX == 1) {
8710         board[fromY][fromX] = EmptySquare;
8711         board[toY][toX] = BlackKing;
8712         board[fromY][0] = EmptySquare;
8713         board[toY][2] = BlackRook;
8714     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8715                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8716                && toY < promoRank
8717                ) {
8718         /* black pawn promotion */
8719         board[toY][toX] = CharToPiece(ToLower(promoChar));
8720         if (board[toY][toX] == EmptySquare) {
8721             board[toY][toX] = BlackQueen;
8722         }
8723         if(gameInfo.variant==VariantBughouse ||
8724            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8725             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8726         board[fromY][fromX] = EmptySquare;
8727     } else if ((fromY == 3)
8728                && (toX != fromX)
8729                && gameInfo.variant != VariantXiangqi
8730                && gameInfo.variant != VariantBerolina
8731                && (board[fromY][fromX] == BlackPawn)
8732                && (board[toY][toX] == EmptySquare)) {
8733         board[fromY][fromX] = EmptySquare;
8734         board[toY][toX] = BlackPawn;
8735         captured = board[toY + 1][toX];
8736         board[toY + 1][toX] = EmptySquare;
8737     } else if ((fromY == 3)
8738                && (toX == fromX)
8739                && gameInfo.variant == VariantBerolina
8740                && (board[fromY][fromX] == BlackPawn)
8741                && (board[toY][toX] == EmptySquare)) {
8742         board[fromY][fromX] = EmptySquare;
8743         board[toY][toX] = BlackPawn;
8744         if(oldEP & EP_BEROLIN_A) {
8745                 captured = board[fromY][fromX-1];
8746                 board[fromY][fromX-1] = EmptySquare;
8747         }else{  captured = board[fromY][fromX+1];
8748                 board[fromY][fromX+1] = EmptySquare;
8749         }
8750     } else {
8751         board[toY][toX] = board[fromY][fromX];
8752         board[fromY][fromX] = EmptySquare;
8753     }
8754   }
8755
8756     if (gameInfo.holdingsWidth != 0) {
8757
8758       /* !!A lot more code needs to be written to support holdings  */
8759       /* [HGM] OK, so I have written it. Holdings are stored in the */
8760       /* penultimate board files, so they are automaticlly stored   */
8761       /* in the game history.                                       */
8762       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8763                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8764         /* Delete from holdings, by decreasing count */
8765         /* and erasing image if necessary            */
8766         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8767         if(p < (int) BlackPawn) { /* white drop */
8768              p -= (int)WhitePawn;
8769                  p = PieceToNumber((ChessSquare)p);
8770              if(p >= gameInfo.holdingsSize) p = 0;
8771              if(--board[p][BOARD_WIDTH-2] <= 0)
8772                   board[p][BOARD_WIDTH-1] = EmptySquare;
8773              if((int)board[p][BOARD_WIDTH-2] < 0)
8774                         board[p][BOARD_WIDTH-2] = 0;
8775         } else {                  /* black drop */
8776              p -= (int)BlackPawn;
8777                  p = PieceToNumber((ChessSquare)p);
8778              if(p >= gameInfo.holdingsSize) p = 0;
8779              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8780                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8781              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8782                         board[BOARD_HEIGHT-1-p][1] = 0;
8783         }
8784       }
8785       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8786           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8787         /* [HGM] holdings: Add to holdings, if holdings exist */
8788         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8789                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8790                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8791         }
8792         p = (int) captured;
8793         if (p >= (int) BlackPawn) {
8794           p -= (int)BlackPawn;
8795           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8796                   /* in Shogi restore piece to its original  first */
8797                   captured = (ChessSquare) (DEMOTED captured);
8798                   p = DEMOTED p;
8799           }
8800           p = PieceToNumber((ChessSquare)p);
8801           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8802           board[p][BOARD_WIDTH-2]++;
8803           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8804         } else {
8805           p -= (int)WhitePawn;
8806           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8807                   captured = (ChessSquare) (DEMOTED captured);
8808                   p = DEMOTED p;
8809           }
8810           p = PieceToNumber((ChessSquare)p);
8811           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8812           board[BOARD_HEIGHT-1-p][1]++;
8813           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8814         }
8815       }
8816     } else if (gameInfo.variant == VariantAtomic) {
8817       if (captured != EmptySquare) {
8818         int y, x;
8819         for (y = toY-1; y <= toY+1; y++) {
8820           for (x = toX-1; x <= toX+1; x++) {
8821             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8822                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8823               board[y][x] = EmptySquare;
8824             }
8825           }
8826         }
8827         board[toY][toX] = EmptySquare;
8828       }
8829     }
8830     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8831         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8832     } else
8833     if(promoChar == '+') {
8834         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8835         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8836     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8837         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8838     }
8839     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8840                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8841         // [HGM] superchess: take promotion piece out of holdings
8842         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8843         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8844             if(!--board[k][BOARD_WIDTH-2])
8845                 board[k][BOARD_WIDTH-1] = EmptySquare;
8846         } else {
8847             if(!--board[BOARD_HEIGHT-1-k][1])
8848                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8849         }
8850     }
8851
8852 }
8853
8854 /* Updates forwardMostMove */
8855 void
8856 MakeMove(fromX, fromY, toX, toY, promoChar)
8857      int fromX, fromY, toX, toY;
8858      int promoChar;
8859 {
8860 //    forwardMostMove++; // [HGM] bare: moved downstream
8861
8862     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8863         int timeLeft; static int lastLoadFlag=0; int king, piece;
8864         piece = boards[forwardMostMove][fromY][fromX];
8865         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8866         if(gameInfo.variant == VariantKnightmate)
8867             king += (int) WhiteUnicorn - (int) WhiteKing;
8868         if(forwardMostMove == 0) {
8869             if(blackPlaysFirst)
8870                 fprintf(serverMoves, "%s;", second.tidy);
8871             fprintf(serverMoves, "%s;", first.tidy);
8872             if(!blackPlaysFirst)
8873                 fprintf(serverMoves, "%s;", second.tidy);
8874         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8875         lastLoadFlag = loadFlag;
8876         // print base move
8877         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8878         // print castling suffix
8879         if( toY == fromY && piece == king ) {
8880             if(toX-fromX > 1)
8881                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8882             if(fromX-toX >1)
8883                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8884         }
8885         // e.p. suffix
8886         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8887              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8888              boards[forwardMostMove][toY][toX] == EmptySquare
8889              && fromX != toX && fromY != toY)
8890                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8891         // promotion suffix
8892         if(promoChar != NULLCHAR)
8893                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8894         if(!loadFlag) {
8895             fprintf(serverMoves, "/%d/%d",
8896                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8897             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8898             else                      timeLeft = blackTimeRemaining/1000;
8899             fprintf(serverMoves, "/%d", timeLeft);
8900         }
8901         fflush(serverMoves);
8902     }
8903
8904     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8905       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8906                         0, 1);
8907       return;
8908     }
8909     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8910     if (commentList[forwardMostMove+1] != NULL) {
8911         free(commentList[forwardMostMove+1]);
8912         commentList[forwardMostMove+1] = NULL;
8913     }
8914     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8915     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8916     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8917     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8918     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8919     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8920     gameInfo.result = GameUnfinished;
8921     if (gameInfo.resultDetails != NULL) {
8922         free(gameInfo.resultDetails);
8923         gameInfo.resultDetails = NULL;
8924     }
8925     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8926                               moveList[forwardMostMove - 1]);
8927     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8928                              PosFlags(forwardMostMove - 1),
8929                              fromY, fromX, toY, toX, promoChar,
8930                              parseList[forwardMostMove - 1]);
8931     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8932       case MT_NONE:
8933       case MT_STALEMATE:
8934       default:
8935         break;
8936       case MT_CHECK:
8937         if(gameInfo.variant != VariantShogi)
8938             strcat(parseList[forwardMostMove - 1], "+");
8939         break;
8940       case MT_CHECKMATE:
8941       case MT_STAINMATE:
8942         strcat(parseList[forwardMostMove - 1], "#");
8943         break;
8944     }
8945     if (appData.debugMode) {
8946         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8947     }
8948
8949 }
8950
8951 /* Updates currentMove if not pausing */
8952 void
8953 ShowMove(fromX, fromY, toX, toY)
8954 {
8955     int instant = (gameMode == PlayFromGameFile) ?
8956         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8957     if(appData.noGUI) return;
8958     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8959         if (!instant) {
8960             if (forwardMostMove == currentMove + 1) {
8961                 AnimateMove(boards[forwardMostMove - 1],
8962                             fromX, fromY, toX, toY);
8963             }
8964             if (appData.highlightLastMove) {
8965                 SetHighlights(fromX, fromY, toX, toY);
8966             }
8967         }
8968         currentMove = forwardMostMove;
8969     }
8970
8971     if (instant) return;
8972
8973     DisplayMove(currentMove - 1);
8974     DrawPosition(FALSE, boards[currentMove]);
8975     DisplayBothClocks();
8976     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8977 }
8978
8979 void SendEgtPath(ChessProgramState *cps)
8980 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8981         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8982
8983         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8984
8985         while(*p) {
8986             char c, *q = name+1, *r, *s;
8987
8988             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8989             while(*p && *p != ',') *q++ = *p++;
8990             *q++ = ':'; *q = 0;
8991             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8992                 strcmp(name, ",nalimov:") == 0 ) {
8993                 // take nalimov path from the menu-changeable option first, if it is defined
8994               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8995                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8996             } else
8997             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8998                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8999                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9000                 s = r = StrStr(s, ":") + 1; // beginning of path info
9001                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9002                 c = *r; *r = 0;             // temporarily null-terminate path info
9003                     *--q = 0;               // strip of trailig ':' from name
9004                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9005                 *r = c;
9006                 SendToProgram(buf,cps);     // send egtbpath command for this format
9007             }
9008             if(*p == ',') p++; // read away comma to position for next format name
9009         }
9010 }
9011
9012 void
9013 InitChessProgram(cps, setup)
9014      ChessProgramState *cps;
9015      int setup; /* [HGM] needed to setup FRC opening position */
9016 {
9017     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9018     if (appData.noChessProgram) return;
9019     hintRequested = FALSE;
9020     bookRequested = FALSE;
9021
9022     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9023     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9024     if(cps->memSize) { /* [HGM] memory */
9025       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9026         SendToProgram(buf, cps);
9027     }
9028     SendEgtPath(cps); /* [HGM] EGT */
9029     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9030       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9031         SendToProgram(buf, cps);
9032     }
9033
9034     SendToProgram(cps->initString, cps);
9035     if (gameInfo.variant != VariantNormal &&
9036         gameInfo.variant != VariantLoadable
9037         /* [HGM] also send variant if board size non-standard */
9038         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9039                                             ) {
9040       char *v = VariantName(gameInfo.variant);
9041       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9042         /* [HGM] in protocol 1 we have to assume all variants valid */
9043         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9044         DisplayFatalError(buf, 0, 1);
9045         return;
9046       }
9047
9048       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9049       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9050       if( gameInfo.variant == VariantXiangqi )
9051            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9052       if( gameInfo.variant == VariantShogi )
9053            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9054       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9055            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9056       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9057           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9058            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9059       if( gameInfo.variant == VariantCourier )
9060            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9061       if( gameInfo.variant == VariantSuper )
9062            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9063       if( gameInfo.variant == VariantGreat )
9064            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9065       if( gameInfo.variant == VariantSChess )
9066            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9067
9068       if(overruled) {
9069         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9070                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9071            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9072            if(StrStr(cps->variants, b) == NULL) {
9073                // specific sized variant not known, check if general sizing allowed
9074                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9075                    if(StrStr(cps->variants, "boardsize") == NULL) {
9076                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9077                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9078                        DisplayFatalError(buf, 0, 1);
9079                        return;
9080                    }
9081                    /* [HGM] here we really should compare with the maximum supported board size */
9082                }
9083            }
9084       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9085       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9086       SendToProgram(buf, cps);
9087     }
9088     currentlyInitializedVariant = gameInfo.variant;
9089
9090     /* [HGM] send opening position in FRC to first engine */
9091     if(setup) {
9092           SendToProgram("force\n", cps);
9093           SendBoard(cps, 0);
9094           /* engine is now in force mode! Set flag to wake it up after first move. */
9095           setboardSpoiledMachineBlack = 1;
9096     }
9097
9098     if (cps->sendICS) {
9099       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9100       SendToProgram(buf, cps);
9101     }
9102     cps->maybeThinking = FALSE;
9103     cps->offeredDraw = 0;
9104     if (!appData.icsActive) {
9105         SendTimeControl(cps, movesPerSession, timeControl,
9106                         timeIncrement, appData.searchDepth,
9107                         searchTime);
9108     }
9109     if (appData.showThinking
9110         // [HGM] thinking: four options require thinking output to be sent
9111         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9112                                 ) {
9113         SendToProgram("post\n", cps);
9114     }
9115     SendToProgram("hard\n", cps);
9116     if (!appData.ponderNextMove) {
9117         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9118            it without being sure what state we are in first.  "hard"
9119            is not a toggle, so that one is OK.
9120          */
9121         SendToProgram("easy\n", cps);
9122     }
9123     if (cps->usePing) {
9124       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9125       SendToProgram(buf, cps);
9126     }
9127     cps->initDone = TRUE;
9128 }
9129
9130
9131 void
9132 StartChessProgram(cps)
9133      ChessProgramState *cps;
9134 {
9135     char buf[MSG_SIZ];
9136     int err;
9137
9138     if (appData.noChessProgram) return;
9139     cps->initDone = FALSE;
9140
9141     if (strcmp(cps->host, "localhost") == 0) {
9142         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9143     } else if (*appData.remoteShell == NULLCHAR) {
9144         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9145     } else {
9146         if (*appData.remoteUser == NULLCHAR) {
9147           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9148                     cps->program);
9149         } else {
9150           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9151                     cps->host, appData.remoteUser, cps->program);
9152         }
9153         err = StartChildProcess(buf, "", &cps->pr);
9154     }
9155
9156     if (err != 0) {
9157       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9158         DisplayFatalError(buf, err, 1);
9159         cps->pr = NoProc;
9160         cps->isr = NULL;
9161         return;
9162     }
9163
9164     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9165     if (cps->protocolVersion > 1) {
9166       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9167       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9168       cps->comboCnt = 0;  //                and values of combo boxes
9169       SendToProgram(buf, cps);
9170     } else {
9171       SendToProgram("xboard\n", cps);
9172     }
9173 }
9174
9175
9176 void
9177 TwoMachinesEventIfReady P((void))
9178 {
9179   if (first.lastPing != first.lastPong) {
9180     DisplayMessage("", _("Waiting for first chess program"));
9181     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9182     return;
9183   }
9184   if (second.lastPing != second.lastPong) {
9185     DisplayMessage("", _("Waiting for second chess program"));
9186     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9187     return;
9188   }
9189   ThawUI();
9190   TwoMachinesEvent();
9191 }
9192
9193 void
9194 NextMatchGame P((void))
9195 {
9196     int index; /* [HGM] autoinc: step load index during match */
9197     Reset(FALSE, TRUE);
9198     if (*appData.loadGameFile != NULLCHAR) {
9199         index = appData.loadGameIndex;
9200         if(index < 0) { // [HGM] autoinc
9201             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9202             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9203         }
9204         LoadGameFromFile(appData.loadGameFile,
9205                          index,
9206                          appData.loadGameFile, FALSE);
9207     } else if (*appData.loadPositionFile != NULLCHAR) {
9208         index = appData.loadPositionIndex;
9209         if(index < 0) { // [HGM] autoinc
9210             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9211             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9212         }
9213         LoadPositionFromFile(appData.loadPositionFile,
9214                              index,
9215                              appData.loadPositionFile);
9216     }
9217     TwoMachinesEventIfReady();
9218 }
9219
9220 void UserAdjudicationEvent( int result )
9221 {
9222     ChessMove gameResult = GameIsDrawn;
9223
9224     if( result > 0 ) {
9225         gameResult = WhiteWins;
9226     }
9227     else if( result < 0 ) {
9228         gameResult = BlackWins;
9229     }
9230
9231     if( gameMode == TwoMachinesPlay ) {
9232         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9233     }
9234 }
9235
9236
9237 // [HGM] save: calculate checksum of game to make games easily identifiable
9238 int StringCheckSum(char *s)
9239 {
9240         int i = 0;
9241         if(s==NULL) return 0;
9242         while(*s) i = i*259 + *s++;
9243         return i;
9244 }
9245
9246 int GameCheckSum()
9247 {
9248         int i, sum=0;
9249         for(i=backwardMostMove; i<forwardMostMove; i++) {
9250                 sum += pvInfoList[i].depth;
9251                 sum += StringCheckSum(parseList[i]);
9252                 sum += StringCheckSum(commentList[i]);
9253                 sum *= 261;
9254         }
9255         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9256         return sum + StringCheckSum(commentList[i]);
9257 } // end of save patch
9258
9259 void
9260 GameEnds(result, resultDetails, whosays)
9261      ChessMove result;
9262      char *resultDetails;
9263      int whosays;
9264 {
9265     GameMode nextGameMode;
9266     int isIcsGame;
9267     char buf[MSG_SIZ], popupRequested = 0;
9268
9269     if(endingGame) return; /* [HGM] crash: forbid recursion */
9270     endingGame = 1;
9271     if(twoBoards) { // [HGM] dual: switch back to one board
9272         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9273         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9274     }
9275     if (appData.debugMode) {
9276       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9277               result, resultDetails ? resultDetails : "(null)", whosays);
9278     }
9279
9280     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9281
9282     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9283         /* If we are playing on ICS, the server decides when the
9284            game is over, but the engine can offer to draw, claim
9285            a draw, or resign.
9286          */
9287 #if ZIPPY
9288         if (appData.zippyPlay && first.initDone) {
9289             if (result == GameIsDrawn) {
9290                 /* In case draw still needs to be claimed */
9291                 SendToICS(ics_prefix);
9292                 SendToICS("draw\n");
9293             } else if (StrCaseStr(resultDetails, "resign")) {
9294                 SendToICS(ics_prefix);
9295                 SendToICS("resign\n");
9296             }
9297         }
9298 #endif
9299         endingGame = 0; /* [HGM] crash */
9300         return;
9301     }
9302
9303     /* If we're loading the game from a file, stop */
9304     if (whosays == GE_FILE) {
9305       (void) StopLoadGameTimer();
9306       gameFileFP = NULL;
9307     }
9308
9309     /* Cancel draw offers */
9310     first.offeredDraw = second.offeredDraw = 0;
9311
9312     /* If this is an ICS game, only ICS can really say it's done;
9313        if not, anyone can. */
9314     isIcsGame = (gameMode == IcsPlayingWhite ||
9315                  gameMode == IcsPlayingBlack ||
9316                  gameMode == IcsObserving    ||
9317                  gameMode == IcsExamining);
9318
9319     if (!isIcsGame || whosays == GE_ICS) {
9320         /* OK -- not an ICS game, or ICS said it was done */
9321         StopClocks();
9322         if (!isIcsGame && !appData.noChessProgram)
9323           SetUserThinkingEnables();
9324
9325         /* [HGM] if a machine claims the game end we verify this claim */
9326         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9327             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9328                 char claimer;
9329                 ChessMove trueResult = (ChessMove) -1;
9330
9331                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9332                                             first.twoMachinesColor[0] :
9333                                             second.twoMachinesColor[0] ;
9334
9335                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9336                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9337                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9338                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9339                 } else
9340                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9341                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9342                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9343                 } else
9344                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9345                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9346                 }
9347
9348                 // now verify win claims, but not in drop games, as we don't understand those yet
9349                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9350                                                  || gameInfo.variant == VariantGreat) &&
9351                     (result == WhiteWins && claimer == 'w' ||
9352                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9353                       if (appData.debugMode) {
9354                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9355                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9356                       }
9357                       if(result != trueResult) {
9358                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9359                               result = claimer == 'w' ? BlackWins : WhiteWins;
9360                               resultDetails = buf;
9361                       }
9362                 } else
9363                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9364                     && (forwardMostMove <= backwardMostMove ||
9365                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9366                         (claimer=='b')==(forwardMostMove&1))
9367                                                                                   ) {
9368                       /* [HGM] verify: draws that were not flagged are false claims */
9369                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9370                       result = claimer == 'w' ? BlackWins : WhiteWins;
9371                       resultDetails = buf;
9372                 }
9373                 /* (Claiming a loss is accepted no questions asked!) */
9374             }
9375             /* [HGM] bare: don't allow bare King to win */
9376             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9377                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9378                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9379                && result != GameIsDrawn)
9380             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9381                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9382                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9383                         if(p >= 0 && p <= (int)WhiteKing) k++;
9384                 }
9385                 if (appData.debugMode) {
9386                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9387                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9388                 }
9389                 if(k <= 1) {
9390                         result = GameIsDrawn;
9391                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9392                         resultDetails = buf;
9393                 }
9394             }
9395         }
9396
9397
9398         if(serverMoves != NULL && !loadFlag) { char c = '=';
9399             if(result==WhiteWins) c = '+';
9400             if(result==BlackWins) c = '-';
9401             if(resultDetails != NULL)
9402                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9403         }
9404         if (resultDetails != NULL) {
9405             gameInfo.result = result;
9406             gameInfo.resultDetails = StrSave(resultDetails);
9407
9408             /* display last move only if game was not loaded from file */
9409             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9410                 DisplayMove(currentMove - 1);
9411
9412             if (forwardMostMove != 0) {
9413                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9414                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9415                                                                 ) {
9416                     if (*appData.saveGameFile != NULLCHAR) {
9417                         SaveGameToFile(appData.saveGameFile, TRUE);
9418                     } else if (appData.autoSaveGames) {
9419                         AutoSaveGame();
9420                     }
9421                     if (*appData.savePositionFile != NULLCHAR) {
9422                         SavePositionToFile(appData.savePositionFile);
9423                     }
9424                 }
9425             }
9426
9427             /* Tell program how game ended in case it is learning */
9428             /* [HGM] Moved this to after saving the PGN, just in case */
9429             /* engine died and we got here through time loss. In that */
9430             /* case we will get a fatal error writing the pipe, which */
9431             /* would otherwise lose us the PGN.                       */
9432             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9433             /* output during GameEnds should never be fatal anymore   */
9434             if (gameMode == MachinePlaysWhite ||
9435                 gameMode == MachinePlaysBlack ||
9436                 gameMode == TwoMachinesPlay ||
9437                 gameMode == IcsPlayingWhite ||
9438                 gameMode == IcsPlayingBlack ||
9439                 gameMode == BeginningOfGame) {
9440                 char buf[MSG_SIZ];
9441                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9442                         resultDetails);
9443                 if (first.pr != NoProc) {
9444                     SendToProgram(buf, &first);
9445                 }
9446                 if (second.pr != NoProc &&
9447                     gameMode == TwoMachinesPlay) {
9448                     SendToProgram(buf, &second);
9449                 }
9450             }
9451         }
9452
9453         if (appData.icsActive) {
9454             if (appData.quietPlay &&
9455                 (gameMode == IcsPlayingWhite ||
9456                  gameMode == IcsPlayingBlack)) {
9457                 SendToICS(ics_prefix);
9458                 SendToICS("set shout 1\n");
9459             }
9460             nextGameMode = IcsIdle;
9461             ics_user_moved = FALSE;
9462             /* clean up premove.  It's ugly when the game has ended and the
9463              * premove highlights are still on the board.
9464              */
9465             if (gotPremove) {
9466               gotPremove = FALSE;
9467               ClearPremoveHighlights();
9468               DrawPosition(FALSE, boards[currentMove]);
9469             }
9470             if (whosays == GE_ICS) {
9471                 switch (result) {
9472                 case WhiteWins:
9473                     if (gameMode == IcsPlayingWhite)
9474                         PlayIcsWinSound();
9475                     else if(gameMode == IcsPlayingBlack)
9476                         PlayIcsLossSound();
9477                     break;
9478                 case BlackWins:
9479                     if (gameMode == IcsPlayingBlack)
9480                         PlayIcsWinSound();
9481                     else if(gameMode == IcsPlayingWhite)
9482                         PlayIcsLossSound();
9483                     break;
9484                 case GameIsDrawn:
9485                     PlayIcsDrawSound();
9486                     break;
9487                 default:
9488                     PlayIcsUnfinishedSound();
9489                 }
9490             }
9491         } else if (gameMode == EditGame ||
9492                    gameMode == PlayFromGameFile ||
9493                    gameMode == AnalyzeMode ||
9494                    gameMode == AnalyzeFile) {
9495             nextGameMode = gameMode;
9496         } else {
9497             nextGameMode = EndOfGame;
9498         }
9499         pausing = FALSE;
9500         ModeHighlight();
9501     } else {
9502         nextGameMode = gameMode;
9503     }
9504
9505     if (appData.noChessProgram) {
9506         gameMode = nextGameMode;
9507         ModeHighlight();
9508         endingGame = 0; /* [HGM] crash */
9509         return;
9510     }
9511
9512     if (first.reuse) {
9513         /* Put first chess program into idle state */
9514         if (first.pr != NoProc &&
9515             (gameMode == MachinePlaysWhite ||
9516              gameMode == MachinePlaysBlack ||
9517              gameMode == TwoMachinesPlay ||
9518              gameMode == IcsPlayingWhite ||
9519              gameMode == IcsPlayingBlack ||
9520              gameMode == BeginningOfGame)) {
9521             SendToProgram("force\n", &first);
9522             if (first.usePing) {
9523               char buf[MSG_SIZ];
9524               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9525               SendToProgram(buf, &first);
9526             }
9527         }
9528     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9529         /* Kill off first chess program */
9530         if (first.isr != NULL)
9531           RemoveInputSource(first.isr);
9532         first.isr = NULL;
9533
9534         if (first.pr != NoProc) {
9535             ExitAnalyzeMode();
9536             DoSleep( appData.delayBeforeQuit );
9537             SendToProgram("quit\n", &first);
9538             DoSleep( appData.delayAfterQuit );
9539             DestroyChildProcess(first.pr, first.useSigterm);
9540         }
9541         first.pr = NoProc;
9542     }
9543     if (second.reuse) {
9544         /* Put second chess program into idle state */
9545         if (second.pr != NoProc &&
9546             gameMode == TwoMachinesPlay) {
9547             SendToProgram("force\n", &second);
9548             if (second.usePing) {
9549               char buf[MSG_SIZ];
9550               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9551               SendToProgram(buf, &second);
9552             }
9553         }
9554     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9555         /* Kill off second chess program */
9556         if (second.isr != NULL)
9557           RemoveInputSource(second.isr);
9558         second.isr = NULL;
9559
9560         if (second.pr != NoProc) {
9561             DoSleep( appData.delayBeforeQuit );
9562             SendToProgram("quit\n", &second);
9563             DoSleep( appData.delayAfterQuit );
9564             DestroyChildProcess(second.pr, second.useSigterm);
9565         }
9566         second.pr = NoProc;
9567     }
9568
9569     if (matchMode && gameMode == TwoMachinesPlay) {
9570         switch (result) {
9571         case WhiteWins:
9572           if (first.twoMachinesColor[0] == 'w') {
9573             first.matchWins++;
9574           } else {
9575             second.matchWins++;
9576           }
9577           break;
9578         case BlackWins:
9579           if (first.twoMachinesColor[0] == 'b') {
9580             first.matchWins++;
9581           } else {
9582             second.matchWins++;
9583           }
9584           break;
9585         default:
9586           break;
9587         }
9588         if (matchGame < appData.matchGames) {
9589             char *tmp;
9590             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9591                 tmp = first.twoMachinesColor;
9592                 first.twoMachinesColor = second.twoMachinesColor;
9593                 second.twoMachinesColor = tmp;
9594             }
9595             gameMode = nextGameMode;
9596             matchGame++;
9597             if(appData.matchPause>10000 || appData.matchPause<10)
9598                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9599             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9600             endingGame = 0; /* [HGM] crash */
9601             return;
9602         } else {
9603             gameMode = nextGameMode;
9604             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9605                      first.tidy, second.tidy,
9606                      first.matchWins, second.matchWins,
9607                      appData.matchGames - (first.matchWins + second.matchWins));
9608             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9609             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9610                 first.twoMachinesColor = "black\n";
9611                 second.twoMachinesColor = "white\n";
9612             } else {
9613                 first.twoMachinesColor = "white\n";
9614                 second.twoMachinesColor = "black\n";
9615             }
9616         }
9617     }
9618     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9619         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9620       ExitAnalyzeMode();
9621     gameMode = nextGameMode;
9622     ModeHighlight();
9623     endingGame = 0;  /* [HGM] crash */
9624     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9625       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9626         matchMode = FALSE; appData.matchGames = matchGame = 0;
9627         DisplayNote(buf);
9628       }
9629     }
9630 }
9631
9632 /* Assumes program was just initialized (initString sent).
9633    Leaves program in force mode. */
9634 void
9635 FeedMovesToProgram(cps, upto)
9636      ChessProgramState *cps;
9637      int upto;
9638 {
9639     int i;
9640
9641     if (appData.debugMode)
9642       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9643               startedFromSetupPosition ? "position and " : "",
9644               backwardMostMove, upto, cps->which);
9645     if(currentlyInitializedVariant != gameInfo.variant) {
9646       char buf[MSG_SIZ];
9647         // [HGM] variantswitch: make engine aware of new variant
9648         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9649                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9650         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9651         SendToProgram(buf, cps);
9652         currentlyInitializedVariant = gameInfo.variant;
9653     }
9654     SendToProgram("force\n", cps);
9655     if (startedFromSetupPosition) {
9656         SendBoard(cps, backwardMostMove);
9657     if (appData.debugMode) {
9658         fprintf(debugFP, "feedMoves\n");
9659     }
9660     }
9661     for (i = backwardMostMove; i < upto; i++) {
9662         SendMoveToProgram(i, cps);
9663     }
9664 }
9665
9666
9667 void
9668 ResurrectChessProgram()
9669 {
9670      /* The chess program may have exited.
9671         If so, restart it and feed it all the moves made so far. */
9672
9673     if (appData.noChessProgram || first.pr != NoProc) return;
9674
9675     StartChessProgram(&first);
9676     InitChessProgram(&first, FALSE);
9677     FeedMovesToProgram(&first, currentMove);
9678
9679     if (!first.sendTime) {
9680         /* can't tell gnuchess what its clock should read,
9681            so we bow to its notion. */
9682         ResetClocks();
9683         timeRemaining[0][currentMove] = whiteTimeRemaining;
9684         timeRemaining[1][currentMove] = blackTimeRemaining;
9685     }
9686
9687     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9688                 appData.icsEngineAnalyze) && first.analysisSupport) {
9689       SendToProgram("analyze\n", &first);
9690       first.analyzing = TRUE;
9691     }
9692 }
9693
9694 /*
9695  * Button procedures
9696  */
9697 void
9698 Reset(redraw, init)
9699      int redraw, init;
9700 {
9701     int i;
9702
9703     if (appData.debugMode) {
9704         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9705                 redraw, init, gameMode);
9706     }
9707     CleanupTail(); // [HGM] vari: delete any stored variations
9708     pausing = pauseExamInvalid = FALSE;
9709     startedFromSetupPosition = blackPlaysFirst = FALSE;
9710     firstMove = TRUE;
9711     whiteFlag = blackFlag = FALSE;
9712     userOfferedDraw = FALSE;
9713     hintRequested = bookRequested = FALSE;
9714     first.maybeThinking = FALSE;
9715     second.maybeThinking = FALSE;
9716     first.bookSuspend = FALSE; // [HGM] book
9717     second.bookSuspend = FALSE;
9718     thinkOutput[0] = NULLCHAR;
9719     lastHint[0] = NULLCHAR;
9720     ClearGameInfo(&gameInfo);
9721     gameInfo.variant = StringToVariant(appData.variant);
9722     ics_user_moved = ics_clock_paused = FALSE;
9723     ics_getting_history = H_FALSE;
9724     ics_gamenum = -1;
9725     white_holding[0] = black_holding[0] = NULLCHAR;
9726     ClearProgramStats();
9727     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9728
9729     ResetFrontEnd();
9730     ClearHighlights();
9731     flipView = appData.flipView;
9732     ClearPremoveHighlights();
9733     gotPremove = FALSE;
9734     alarmSounded = FALSE;
9735
9736     GameEnds(EndOfFile, NULL, GE_PLAYER);
9737     if(appData.serverMovesName != NULL) {
9738         /* [HGM] prepare to make moves file for broadcasting */
9739         clock_t t = clock();
9740         if(serverMoves != NULL) fclose(serverMoves);
9741         serverMoves = fopen(appData.serverMovesName, "r");
9742         if(serverMoves != NULL) {
9743             fclose(serverMoves);
9744             /* delay 15 sec before overwriting, so all clients can see end */
9745             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9746         }
9747         serverMoves = fopen(appData.serverMovesName, "w");
9748     }
9749
9750     ExitAnalyzeMode();
9751     gameMode = BeginningOfGame;
9752     ModeHighlight();
9753     if(appData.icsActive) gameInfo.variant = VariantNormal;
9754     currentMove = forwardMostMove = backwardMostMove = 0;
9755     InitPosition(redraw);
9756     for (i = 0; i < MAX_MOVES; i++) {
9757         if (commentList[i] != NULL) {
9758             free(commentList[i]);
9759             commentList[i] = NULL;
9760         }
9761     }
9762     ResetClocks();
9763     timeRemaining[0][0] = whiteTimeRemaining;
9764     timeRemaining[1][0] = blackTimeRemaining;
9765     if (first.pr == NULL) {
9766         StartChessProgram(&first);
9767     }
9768     if (init) {
9769             InitChessProgram(&first, startedFromSetupPosition);
9770     }
9771     DisplayTitle("");
9772     DisplayMessage("", "");
9773     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9774     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9775 }
9776
9777 void
9778 AutoPlayGameLoop()
9779 {
9780     for (;;) {
9781         if (!AutoPlayOneMove())
9782           return;
9783         if (matchMode || appData.timeDelay == 0)
9784           continue;
9785         if (appData.timeDelay < 0)
9786           return;
9787         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9788         break;
9789     }
9790 }
9791
9792
9793 int
9794 AutoPlayOneMove()
9795 {
9796     int fromX, fromY, toX, toY;
9797
9798     if (appData.debugMode) {
9799       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9800     }
9801
9802     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9803       return FALSE;
9804
9805     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9806       pvInfoList[currentMove].depth = programStats.depth;
9807       pvInfoList[currentMove].score = programStats.score;
9808       pvInfoList[currentMove].time  = 0;
9809       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9810     }
9811
9812     if (currentMove >= forwardMostMove) {
9813       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9814       gameMode = EditGame;
9815       ModeHighlight();
9816
9817       /* [AS] Clear current move marker at the end of a game */
9818       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9819
9820       return FALSE;
9821     }
9822
9823     toX = moveList[currentMove][2] - AAA;
9824     toY = moveList[currentMove][3] - ONE;
9825
9826     if (moveList[currentMove][1] == '@') {
9827         if (appData.highlightLastMove) {
9828             SetHighlights(-1, -1, toX, toY);
9829         }
9830     } else {
9831         fromX = moveList[currentMove][0] - AAA;
9832         fromY = moveList[currentMove][1] - ONE;
9833
9834         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9835
9836         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9837
9838         if (appData.highlightLastMove) {
9839             SetHighlights(fromX, fromY, toX, toY);
9840         }
9841     }
9842     DisplayMove(currentMove);
9843     SendMoveToProgram(currentMove++, &first);
9844     DisplayBothClocks();
9845     DrawPosition(FALSE, boards[currentMove]);
9846     // [HGM] PV info: always display, routine tests if empty
9847     DisplayComment(currentMove - 1, commentList[currentMove]);
9848     return TRUE;
9849 }
9850
9851
9852 int
9853 LoadGameOneMove(readAhead)
9854      ChessMove readAhead;
9855 {
9856     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9857     char promoChar = NULLCHAR;
9858     ChessMove moveType;
9859     char move[MSG_SIZ];
9860     char *p, *q;
9861
9862     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9863         gameMode != AnalyzeMode && gameMode != Training) {
9864         gameFileFP = NULL;
9865         return FALSE;
9866     }
9867
9868     yyboardindex = forwardMostMove;
9869     if (readAhead != EndOfFile) {
9870       moveType = readAhead;
9871     } else {
9872       if (gameFileFP == NULL)
9873           return FALSE;
9874       moveType = (ChessMove) Myylex();
9875     }
9876
9877     done = FALSE;
9878     switch (moveType) {
9879       case Comment:
9880         if (appData.debugMode)
9881           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9882         p = yy_text;
9883
9884         /* append the comment but don't display it */
9885         AppendComment(currentMove, p, FALSE);
9886         return TRUE;
9887
9888       case WhiteCapturesEnPassant:
9889       case BlackCapturesEnPassant:
9890       case WhitePromotion:
9891       case BlackPromotion:
9892       case WhiteNonPromotion:
9893       case BlackNonPromotion:
9894       case NormalMove:
9895       case WhiteKingSideCastle:
9896       case WhiteQueenSideCastle:
9897       case BlackKingSideCastle:
9898       case BlackQueenSideCastle:
9899       case WhiteKingSideCastleWild:
9900       case WhiteQueenSideCastleWild:
9901       case BlackKingSideCastleWild:
9902       case BlackQueenSideCastleWild:
9903       /* PUSH Fabien */
9904       case WhiteHSideCastleFR:
9905       case WhiteASideCastleFR:
9906       case BlackHSideCastleFR:
9907       case BlackASideCastleFR:
9908       /* POP Fabien */
9909         if (appData.debugMode)
9910           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9911         fromX = currentMoveString[0] - AAA;
9912         fromY = currentMoveString[1] - ONE;
9913         toX = currentMoveString[2] - AAA;
9914         toY = currentMoveString[3] - ONE;
9915         promoChar = currentMoveString[4];
9916         break;
9917
9918       case WhiteDrop:
9919       case BlackDrop:
9920         if (appData.debugMode)
9921           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9922         fromX = moveType == WhiteDrop ?
9923           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9924         (int) CharToPiece(ToLower(currentMoveString[0]));
9925         fromY = DROP_RANK;
9926         toX = currentMoveString[2] - AAA;
9927         toY = currentMoveString[3] - ONE;
9928         break;
9929
9930       case WhiteWins:
9931       case BlackWins:
9932       case GameIsDrawn:
9933       case GameUnfinished:
9934         if (appData.debugMode)
9935           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9936         p = strchr(yy_text, '{');
9937         if (p == NULL) p = strchr(yy_text, '(');
9938         if (p == NULL) {
9939             p = yy_text;
9940             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9941         } else {
9942             q = strchr(p, *p == '{' ? '}' : ')');
9943             if (q != NULL) *q = NULLCHAR;
9944             p++;
9945         }
9946         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9947         GameEnds(moveType, p, GE_FILE);
9948         done = TRUE;
9949         if (cmailMsgLoaded) {
9950             ClearHighlights();
9951             flipView = WhiteOnMove(currentMove);
9952             if (moveType == GameUnfinished) flipView = !flipView;
9953             if (appData.debugMode)
9954               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9955         }
9956         break;
9957
9958       case EndOfFile:
9959         if (appData.debugMode)
9960           fprintf(debugFP, "Parser hit end of file\n");
9961         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9962           case MT_NONE:
9963           case MT_CHECK:
9964             break;
9965           case MT_CHECKMATE:
9966           case MT_STAINMATE:
9967             if (WhiteOnMove(currentMove)) {
9968                 GameEnds(BlackWins, "Black mates", GE_FILE);
9969             } else {
9970                 GameEnds(WhiteWins, "White mates", GE_FILE);
9971             }
9972             break;
9973           case MT_STALEMATE:
9974             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9975             break;
9976         }
9977         done = TRUE;
9978         break;
9979
9980       case MoveNumberOne:
9981         if (lastLoadGameStart == GNUChessGame) {
9982             /* GNUChessGames have numbers, but they aren't move numbers */
9983             if (appData.debugMode)
9984               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9985                       yy_text, (int) moveType);
9986             return LoadGameOneMove(EndOfFile); /* tail recursion */
9987         }
9988         /* else fall thru */
9989
9990       case XBoardGame:
9991       case GNUChessGame:
9992       case PGNTag:
9993         /* Reached start of next game in file */
9994         if (appData.debugMode)
9995           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9996         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9997           case MT_NONE:
9998           case MT_CHECK:
9999             break;
10000           case MT_CHECKMATE:
10001           case MT_STAINMATE:
10002             if (WhiteOnMove(currentMove)) {
10003                 GameEnds(BlackWins, "Black mates", GE_FILE);
10004             } else {
10005                 GameEnds(WhiteWins, "White mates", GE_FILE);
10006             }
10007             break;
10008           case MT_STALEMATE:
10009             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10010             break;
10011         }
10012         done = TRUE;
10013         break;
10014
10015       case PositionDiagram:     /* should not happen; ignore */
10016       case ElapsedTime:         /* ignore */
10017       case NAG:                 /* ignore */
10018         if (appData.debugMode)
10019           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10020                   yy_text, (int) moveType);
10021         return LoadGameOneMove(EndOfFile); /* tail recursion */
10022
10023       case IllegalMove:
10024         if (appData.testLegality) {
10025             if (appData.debugMode)
10026               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10027             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10028                     (forwardMostMove / 2) + 1,
10029                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10030             DisplayError(move, 0);
10031             done = TRUE;
10032         } else {
10033             if (appData.debugMode)
10034               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10035                       yy_text, currentMoveString);
10036             fromX = currentMoveString[0] - AAA;
10037             fromY = currentMoveString[1] - ONE;
10038             toX = currentMoveString[2] - AAA;
10039             toY = currentMoveString[3] - ONE;
10040             promoChar = currentMoveString[4];
10041         }
10042         break;
10043
10044       case AmbiguousMove:
10045         if (appData.debugMode)
10046           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10047         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10048                 (forwardMostMove / 2) + 1,
10049                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10050         DisplayError(move, 0);
10051         done = TRUE;
10052         break;
10053
10054       default:
10055       case ImpossibleMove:
10056         if (appData.debugMode)
10057           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10058         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10059                 (forwardMostMove / 2) + 1,
10060                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10061         DisplayError(move, 0);
10062         done = TRUE;
10063         break;
10064     }
10065
10066     if (done) {
10067         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10068             DrawPosition(FALSE, boards[currentMove]);
10069             DisplayBothClocks();
10070             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10071               DisplayComment(currentMove - 1, commentList[currentMove]);
10072         }
10073         (void) StopLoadGameTimer();
10074         gameFileFP = NULL;
10075         cmailOldMove = forwardMostMove;
10076         return FALSE;
10077     } else {
10078         /* currentMoveString is set as a side-effect of yylex */
10079
10080         thinkOutput[0] = NULLCHAR;
10081         MakeMove(fromX, fromY, toX, toY, promoChar);
10082         currentMove = forwardMostMove;
10083         return TRUE;
10084     }
10085 }
10086
10087 /* Load the nth game from the given file */
10088 int
10089 LoadGameFromFile(filename, n, title, useList)
10090      char *filename;
10091      int n;
10092      char *title;
10093      /*Boolean*/ int useList;
10094 {
10095     FILE *f;
10096     char buf[MSG_SIZ];
10097
10098     if (strcmp(filename, "-") == 0) {
10099         f = stdin;
10100         title = "stdin";
10101     } else {
10102         f = fopen(filename, "rb");
10103         if (f == NULL) {
10104           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10105             DisplayError(buf, errno);
10106             return FALSE;
10107         }
10108     }
10109     if (fseek(f, 0, 0) == -1) {
10110         /* f is not seekable; probably a pipe */
10111         useList = FALSE;
10112     }
10113     if (useList && n == 0) {
10114         int error = GameListBuild(f);
10115         if (error) {
10116             DisplayError(_("Cannot build game list"), error);
10117         } else if (!ListEmpty(&gameList) &&
10118                    ((ListGame *) gameList.tailPred)->number > 1) {
10119             GameListPopUp(f, title);
10120             return TRUE;
10121         }
10122         GameListDestroy();
10123         n = 1;
10124     }
10125     if (n == 0) n = 1;
10126     return LoadGame(f, n, title, FALSE);
10127 }
10128
10129
10130 void
10131 MakeRegisteredMove()
10132 {
10133     int fromX, fromY, toX, toY;
10134     char promoChar;
10135     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10136         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10137           case CMAIL_MOVE:
10138           case CMAIL_DRAW:
10139             if (appData.debugMode)
10140               fprintf(debugFP, "Restoring %s for game %d\n",
10141                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10142
10143             thinkOutput[0] = NULLCHAR;
10144             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10145             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10146             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10147             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10148             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10149             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10150             MakeMove(fromX, fromY, toX, toY, promoChar);
10151             ShowMove(fromX, fromY, toX, toY);
10152
10153             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10154               case MT_NONE:
10155               case MT_CHECK:
10156                 break;
10157
10158               case MT_CHECKMATE:
10159               case MT_STAINMATE:
10160                 if (WhiteOnMove(currentMove)) {
10161                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10162                 } else {
10163                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10164                 }
10165                 break;
10166
10167               case MT_STALEMATE:
10168                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10169                 break;
10170             }
10171
10172             break;
10173
10174           case CMAIL_RESIGN:
10175             if (WhiteOnMove(currentMove)) {
10176                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10177             } else {
10178                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10179             }
10180             break;
10181
10182           case CMAIL_ACCEPT:
10183             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10184             break;
10185
10186           default:
10187             break;
10188         }
10189     }
10190
10191     return;
10192 }
10193
10194 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10195 int
10196 CmailLoadGame(f, gameNumber, title, useList)
10197      FILE *f;
10198      int gameNumber;
10199      char *title;
10200      int useList;
10201 {
10202     int retVal;
10203
10204     if (gameNumber > nCmailGames) {
10205         DisplayError(_("No more games in this message"), 0);
10206         return FALSE;
10207     }
10208     if (f == lastLoadGameFP) {
10209         int offset = gameNumber - lastLoadGameNumber;
10210         if (offset == 0) {
10211             cmailMsg[0] = NULLCHAR;
10212             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10213                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10214                 nCmailMovesRegistered--;
10215             }
10216             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10217             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10218                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10219             }
10220         } else {
10221             if (! RegisterMove()) return FALSE;
10222         }
10223     }
10224
10225     retVal = LoadGame(f, gameNumber, title, useList);
10226
10227     /* Make move registered during previous look at this game, if any */
10228     MakeRegisteredMove();
10229
10230     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10231         commentList[currentMove]
10232           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10233         DisplayComment(currentMove - 1, commentList[currentMove]);
10234     }
10235
10236     return retVal;
10237 }
10238
10239 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10240 int
10241 ReloadGame(offset)
10242      int offset;
10243 {
10244     int gameNumber = lastLoadGameNumber + offset;
10245     if (lastLoadGameFP == NULL) {
10246         DisplayError(_("No game has been loaded yet"), 0);
10247         return FALSE;
10248     }
10249     if (gameNumber <= 0) {
10250         DisplayError(_("Can't back up any further"), 0);
10251         return FALSE;
10252     }
10253     if (cmailMsgLoaded) {
10254         return CmailLoadGame(lastLoadGameFP, gameNumber,
10255                              lastLoadGameTitle, lastLoadGameUseList);
10256     } else {
10257         return LoadGame(lastLoadGameFP, gameNumber,
10258                         lastLoadGameTitle, lastLoadGameUseList);
10259     }
10260 }
10261
10262
10263
10264 /* Load the nth game from open file f */
10265 int
10266 LoadGame(f, gameNumber, title, useList)
10267      FILE *f;
10268      int gameNumber;
10269      char *title;
10270      int useList;
10271 {
10272     ChessMove cm;
10273     char buf[MSG_SIZ];
10274     int gn = gameNumber;
10275     ListGame *lg = NULL;
10276     int numPGNTags = 0;
10277     int err;
10278     GameMode oldGameMode;
10279     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10280
10281     if (appData.debugMode)
10282         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10283
10284     if (gameMode == Training )
10285         SetTrainingModeOff();
10286
10287     oldGameMode = gameMode;
10288     if (gameMode != BeginningOfGame) {
10289       Reset(FALSE, TRUE);
10290     }
10291
10292     gameFileFP = f;
10293     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10294         fclose(lastLoadGameFP);
10295     }
10296
10297     if (useList) {
10298         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10299
10300         if (lg) {
10301             fseek(f, lg->offset, 0);
10302             GameListHighlight(gameNumber);
10303             gn = 1;
10304         }
10305         else {
10306             DisplayError(_("Game number out of range"), 0);
10307             return FALSE;
10308         }
10309     } else {
10310         GameListDestroy();
10311         if (fseek(f, 0, 0) == -1) {
10312             if (f == lastLoadGameFP ?
10313                 gameNumber == lastLoadGameNumber + 1 :
10314                 gameNumber == 1) {
10315                 gn = 1;
10316             } else {
10317                 DisplayError(_("Can't seek on game file"), 0);
10318                 return FALSE;
10319             }
10320         }
10321     }
10322     lastLoadGameFP = f;
10323     lastLoadGameNumber = gameNumber;
10324     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10325     lastLoadGameUseList = useList;
10326
10327     yynewfile(f);
10328
10329     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10330       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10331                 lg->gameInfo.black);
10332             DisplayTitle(buf);
10333     } else if (*title != NULLCHAR) {
10334         if (gameNumber > 1) {
10335           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10336             DisplayTitle(buf);
10337         } else {
10338             DisplayTitle(title);
10339         }
10340     }
10341
10342     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10343         gameMode = PlayFromGameFile;
10344         ModeHighlight();
10345     }
10346
10347     currentMove = forwardMostMove = backwardMostMove = 0;
10348     CopyBoard(boards[0], initialPosition);
10349     StopClocks();
10350
10351     /*
10352      * Skip the first gn-1 games in the file.
10353      * Also skip over anything that precedes an identifiable
10354      * start of game marker, to avoid being confused by
10355      * garbage at the start of the file.  Currently
10356      * recognized start of game markers are the move number "1",
10357      * the pattern "gnuchess .* game", the pattern
10358      * "^[#;%] [^ ]* game file", and a PGN tag block.
10359      * A game that starts with one of the latter two patterns
10360      * will also have a move number 1, possibly
10361      * following a position diagram.
10362      * 5-4-02: Let's try being more lenient and allowing a game to
10363      * start with an unnumbered move.  Does that break anything?
10364      */
10365     cm = lastLoadGameStart = EndOfFile;
10366     while (gn > 0) {
10367         yyboardindex = forwardMostMove;
10368         cm = (ChessMove) Myylex();
10369         switch (cm) {
10370           case EndOfFile:
10371             if (cmailMsgLoaded) {
10372                 nCmailGames = CMAIL_MAX_GAMES - gn;
10373             } else {
10374                 Reset(TRUE, TRUE);
10375                 DisplayError(_("Game not found in file"), 0);
10376             }
10377             return FALSE;
10378
10379           case GNUChessGame:
10380           case XBoardGame:
10381             gn--;
10382             lastLoadGameStart = cm;
10383             break;
10384
10385           case MoveNumberOne:
10386             switch (lastLoadGameStart) {
10387               case GNUChessGame:
10388               case XBoardGame:
10389               case PGNTag:
10390                 break;
10391               case MoveNumberOne:
10392               case EndOfFile:
10393                 gn--;           /* count this game */
10394                 lastLoadGameStart = cm;
10395                 break;
10396               default:
10397                 /* impossible */
10398                 break;
10399             }
10400             break;
10401
10402           case PGNTag:
10403             switch (lastLoadGameStart) {
10404               case GNUChessGame:
10405               case PGNTag:
10406               case MoveNumberOne:
10407               case EndOfFile:
10408                 gn--;           /* count this game */
10409                 lastLoadGameStart = cm;
10410                 break;
10411               case XBoardGame:
10412                 lastLoadGameStart = cm; /* game counted already */
10413                 break;
10414               default:
10415                 /* impossible */
10416                 break;
10417             }
10418             if (gn > 0) {
10419                 do {
10420                     yyboardindex = forwardMostMove;
10421                     cm = (ChessMove) Myylex();
10422                 } while (cm == PGNTag || cm == Comment);
10423             }
10424             break;
10425
10426           case WhiteWins:
10427           case BlackWins:
10428           case GameIsDrawn:
10429             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10430                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10431                     != CMAIL_OLD_RESULT) {
10432                     nCmailResults ++ ;
10433                     cmailResult[  CMAIL_MAX_GAMES
10434                                 - gn - 1] = CMAIL_OLD_RESULT;
10435                 }
10436             }
10437             break;
10438
10439           case NormalMove:
10440             /* Only a NormalMove can be at the start of a game
10441              * without a position diagram. */
10442             if (lastLoadGameStart == EndOfFile ) {
10443               gn--;
10444               lastLoadGameStart = MoveNumberOne;
10445             }
10446             break;
10447
10448           default:
10449             break;
10450         }
10451     }
10452
10453     if (appData.debugMode)
10454       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10455
10456     if (cm == XBoardGame) {
10457         /* Skip any header junk before position diagram and/or move 1 */
10458         for (;;) {
10459             yyboardindex = forwardMostMove;
10460             cm = (ChessMove) Myylex();
10461
10462             if (cm == EndOfFile ||
10463                 cm == GNUChessGame || cm == XBoardGame) {
10464                 /* Empty game; pretend end-of-file and handle later */
10465                 cm = EndOfFile;
10466                 break;
10467             }
10468
10469             if (cm == MoveNumberOne || cm == PositionDiagram ||
10470                 cm == PGNTag || cm == Comment)
10471               break;
10472         }
10473     } else if (cm == GNUChessGame) {
10474         if (gameInfo.event != NULL) {
10475             free(gameInfo.event);
10476         }
10477         gameInfo.event = StrSave(yy_text);
10478     }
10479
10480     startedFromSetupPosition = FALSE;
10481     while (cm == PGNTag) {
10482         if (appData.debugMode)
10483           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10484         err = ParsePGNTag(yy_text, &gameInfo);
10485         if (!err) numPGNTags++;
10486
10487         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10488         if(gameInfo.variant != oldVariant) {
10489             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10490             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10491             InitPosition(TRUE);
10492             oldVariant = gameInfo.variant;
10493             if (appData.debugMode)
10494               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10495         }
10496
10497
10498         if (gameInfo.fen != NULL) {
10499           Board initial_position;
10500           startedFromSetupPosition = TRUE;
10501           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10502             Reset(TRUE, TRUE);
10503             DisplayError(_("Bad FEN position in file"), 0);
10504             return FALSE;
10505           }
10506           CopyBoard(boards[0], initial_position);
10507           if (blackPlaysFirst) {
10508             currentMove = forwardMostMove = backwardMostMove = 1;
10509             CopyBoard(boards[1], initial_position);
10510             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10511             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10512             timeRemaining[0][1] = whiteTimeRemaining;
10513             timeRemaining[1][1] = blackTimeRemaining;
10514             if (commentList[0] != NULL) {
10515               commentList[1] = commentList[0];
10516               commentList[0] = NULL;
10517             }
10518           } else {
10519             currentMove = forwardMostMove = backwardMostMove = 0;
10520           }
10521           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10522           {   int i;
10523               initialRulePlies = FENrulePlies;
10524               for( i=0; i< nrCastlingRights; i++ )
10525                   initialRights[i] = initial_position[CASTLING][i];
10526           }
10527           yyboardindex = forwardMostMove;
10528           free(gameInfo.fen);
10529           gameInfo.fen = NULL;
10530         }
10531
10532         yyboardindex = forwardMostMove;
10533         cm = (ChessMove) Myylex();
10534
10535         /* Handle comments interspersed among the tags */
10536         while (cm == Comment) {
10537             char *p;
10538             if (appData.debugMode)
10539               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10540             p = yy_text;
10541             AppendComment(currentMove, p, FALSE);
10542             yyboardindex = forwardMostMove;
10543             cm = (ChessMove) Myylex();
10544         }
10545     }
10546
10547     /* don't rely on existence of Event tag since if game was
10548      * pasted from clipboard the Event tag may not exist
10549      */
10550     if (numPGNTags > 0){
10551         char *tags;
10552         if (gameInfo.variant == VariantNormal) {
10553           VariantClass v = StringToVariant(gameInfo.event);
10554           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10555           if(v < VariantShogi) gameInfo.variant = v;
10556         }
10557         if (!matchMode) {
10558           if( appData.autoDisplayTags ) {
10559             tags = PGNTags(&gameInfo);
10560             TagsPopUp(tags, CmailMsg());
10561             free(tags);
10562           }
10563         }
10564     } else {
10565         /* Make something up, but don't display it now */
10566         SetGameInfo();
10567         TagsPopDown();
10568     }
10569
10570     if (cm == PositionDiagram) {
10571         int i, j;
10572         char *p;
10573         Board initial_position;
10574
10575         if (appData.debugMode)
10576           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10577
10578         if (!startedFromSetupPosition) {
10579             p = yy_text;
10580             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10581               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10582                 switch (*p) {
10583                   case '{':
10584                   case '[':
10585                   case '-':
10586                   case ' ':
10587                   case '\t':
10588                   case '\n':
10589                   case '\r':
10590                     break;
10591                   default:
10592                     initial_position[i][j++] = CharToPiece(*p);
10593                     break;
10594                 }
10595             while (*p == ' ' || *p == '\t' ||
10596                    *p == '\n' || *p == '\r') p++;
10597
10598             if (strncmp(p, "black", strlen("black"))==0)
10599               blackPlaysFirst = TRUE;
10600             else
10601               blackPlaysFirst = FALSE;
10602             startedFromSetupPosition = TRUE;
10603
10604             CopyBoard(boards[0], initial_position);
10605             if (blackPlaysFirst) {
10606                 currentMove = forwardMostMove = backwardMostMove = 1;
10607                 CopyBoard(boards[1], initial_position);
10608                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10609                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10610                 timeRemaining[0][1] = whiteTimeRemaining;
10611                 timeRemaining[1][1] = blackTimeRemaining;
10612                 if (commentList[0] != NULL) {
10613                     commentList[1] = commentList[0];
10614                     commentList[0] = NULL;
10615                 }
10616             } else {
10617                 currentMove = forwardMostMove = backwardMostMove = 0;
10618             }
10619         }
10620         yyboardindex = forwardMostMove;
10621         cm = (ChessMove) Myylex();
10622     }
10623
10624     if (first.pr == NoProc) {
10625         StartChessProgram(&first);
10626     }
10627     InitChessProgram(&first, FALSE);
10628     SendToProgram("force\n", &first);
10629     if (startedFromSetupPosition) {
10630         SendBoard(&first, forwardMostMove);
10631     if (appData.debugMode) {
10632         fprintf(debugFP, "Load Game\n");
10633     }
10634         DisplayBothClocks();
10635     }
10636
10637     /* [HGM] server: flag to write setup moves in broadcast file as one */
10638     loadFlag = appData.suppressLoadMoves;
10639
10640     while (cm == Comment) {
10641         char *p;
10642         if (appData.debugMode)
10643           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10644         p = yy_text;
10645         AppendComment(currentMove, p, FALSE);
10646         yyboardindex = forwardMostMove;
10647         cm = (ChessMove) Myylex();
10648     }
10649
10650     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10651         cm == WhiteWins || cm == BlackWins ||
10652         cm == GameIsDrawn || cm == GameUnfinished) {
10653         DisplayMessage("", _("No moves in game"));
10654         if (cmailMsgLoaded) {
10655             if (appData.debugMode)
10656               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10657             ClearHighlights();
10658             flipView = FALSE;
10659         }
10660         DrawPosition(FALSE, boards[currentMove]);
10661         DisplayBothClocks();
10662         gameMode = EditGame;
10663         ModeHighlight();
10664         gameFileFP = NULL;
10665         cmailOldMove = 0;
10666         return TRUE;
10667     }
10668
10669     // [HGM] PV info: routine tests if comment empty
10670     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10671         DisplayComment(currentMove - 1, commentList[currentMove]);
10672     }
10673     if (!matchMode && appData.timeDelay != 0)
10674       DrawPosition(FALSE, boards[currentMove]);
10675
10676     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10677       programStats.ok_to_send = 1;
10678     }
10679
10680     /* if the first token after the PGN tags is a move
10681      * and not move number 1, retrieve it from the parser
10682      */
10683     if (cm != MoveNumberOne)
10684         LoadGameOneMove(cm);
10685
10686     /* load the remaining moves from the file */
10687     while (LoadGameOneMove(EndOfFile)) {
10688       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10689       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10690     }
10691
10692     /* rewind to the start of the game */
10693     currentMove = backwardMostMove;
10694
10695     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10696
10697     if (oldGameMode == AnalyzeFile ||
10698         oldGameMode == AnalyzeMode) {
10699       AnalyzeFileEvent();
10700     }
10701
10702     if (matchMode || appData.timeDelay == 0) {
10703       ToEndEvent();
10704       gameMode = EditGame;
10705       ModeHighlight();
10706     } else if (appData.timeDelay > 0) {
10707       AutoPlayGameLoop();
10708     }
10709
10710     if (appData.debugMode)
10711         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10712
10713     loadFlag = 0; /* [HGM] true game starts */
10714     return TRUE;
10715 }
10716
10717 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10718 int
10719 ReloadPosition(offset)
10720      int offset;
10721 {
10722     int positionNumber = lastLoadPositionNumber + offset;
10723     if (lastLoadPositionFP == NULL) {
10724         DisplayError(_("No position has been loaded yet"), 0);
10725         return FALSE;
10726     }
10727     if (positionNumber <= 0) {
10728         DisplayError(_("Can't back up any further"), 0);
10729         return FALSE;
10730     }
10731     return LoadPosition(lastLoadPositionFP, positionNumber,
10732                         lastLoadPositionTitle);
10733 }
10734
10735 /* Load the nth position from the given file */
10736 int
10737 LoadPositionFromFile(filename, n, title)
10738      char *filename;
10739      int n;
10740      char *title;
10741 {
10742     FILE *f;
10743     char buf[MSG_SIZ];
10744
10745     if (strcmp(filename, "-") == 0) {
10746         return LoadPosition(stdin, n, "stdin");
10747     } else {
10748         f = fopen(filename, "rb");
10749         if (f == NULL) {
10750             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10751             DisplayError(buf, errno);
10752             return FALSE;
10753         } else {
10754             return LoadPosition(f, n, title);
10755         }
10756     }
10757 }
10758
10759 /* Load the nth position from the given open file, and close it */
10760 int
10761 LoadPosition(f, positionNumber, title)
10762      FILE *f;
10763      int positionNumber;
10764      char *title;
10765 {
10766     char *p, line[MSG_SIZ];
10767     Board initial_position;
10768     int i, j, fenMode, pn;
10769
10770     if (gameMode == Training )
10771         SetTrainingModeOff();
10772
10773     if (gameMode != BeginningOfGame) {
10774         Reset(FALSE, TRUE);
10775     }
10776     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10777         fclose(lastLoadPositionFP);
10778     }
10779     if (positionNumber == 0) positionNumber = 1;
10780     lastLoadPositionFP = f;
10781     lastLoadPositionNumber = positionNumber;
10782     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10783     if (first.pr == NoProc) {
10784       StartChessProgram(&first);
10785       InitChessProgram(&first, FALSE);
10786     }
10787     pn = positionNumber;
10788     if (positionNumber < 0) {
10789         /* Negative position number means to seek to that byte offset */
10790         if (fseek(f, -positionNumber, 0) == -1) {
10791             DisplayError(_("Can't seek on position file"), 0);
10792             return FALSE;
10793         };
10794         pn = 1;
10795     } else {
10796         if (fseek(f, 0, 0) == -1) {
10797             if (f == lastLoadPositionFP ?
10798                 positionNumber == lastLoadPositionNumber + 1 :
10799                 positionNumber == 1) {
10800                 pn = 1;
10801             } else {
10802                 DisplayError(_("Can't seek on position file"), 0);
10803                 return FALSE;
10804             }
10805         }
10806     }
10807     /* See if this file is FEN or old-style xboard */
10808     if (fgets(line, MSG_SIZ, f) == NULL) {
10809         DisplayError(_("Position not found in file"), 0);
10810         return FALSE;
10811     }
10812     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10813     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10814
10815     if (pn >= 2) {
10816         if (fenMode || line[0] == '#') pn--;
10817         while (pn > 0) {
10818             /* skip positions before number pn */
10819             if (fgets(line, MSG_SIZ, f) == NULL) {
10820                 Reset(TRUE, TRUE);
10821                 DisplayError(_("Position not found in file"), 0);
10822                 return FALSE;
10823             }
10824             if (fenMode || line[0] == '#') pn--;
10825         }
10826     }
10827
10828     if (fenMode) {
10829         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10830             DisplayError(_("Bad FEN position in file"), 0);
10831             return FALSE;
10832         }
10833     } else {
10834         (void) fgets(line, MSG_SIZ, f);
10835         (void) fgets(line, MSG_SIZ, f);
10836
10837         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10838             (void) fgets(line, MSG_SIZ, f);
10839             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10840                 if (*p == ' ')
10841                   continue;
10842                 initial_position[i][j++] = CharToPiece(*p);
10843             }
10844         }
10845
10846         blackPlaysFirst = FALSE;
10847         if (!feof(f)) {
10848             (void) fgets(line, MSG_SIZ, f);
10849             if (strncmp(line, "black", strlen("black"))==0)
10850               blackPlaysFirst = TRUE;
10851         }
10852     }
10853     startedFromSetupPosition = TRUE;
10854
10855     SendToProgram("force\n", &first);
10856     CopyBoard(boards[0], initial_position);
10857     if (blackPlaysFirst) {
10858         currentMove = forwardMostMove = backwardMostMove = 1;
10859         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10860         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10861         CopyBoard(boards[1], initial_position);
10862         DisplayMessage("", _("Black to play"));
10863     } else {
10864         currentMove = forwardMostMove = backwardMostMove = 0;
10865         DisplayMessage("", _("White to play"));
10866     }
10867     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10868     SendBoard(&first, forwardMostMove);
10869     if (appData.debugMode) {
10870 int i, j;
10871   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10872   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10873         fprintf(debugFP, "Load Position\n");
10874     }
10875
10876     if (positionNumber > 1) {
10877       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10878         DisplayTitle(line);
10879     } else {
10880         DisplayTitle(title);
10881     }
10882     gameMode = EditGame;
10883     ModeHighlight();
10884     ResetClocks();
10885     timeRemaining[0][1] = whiteTimeRemaining;
10886     timeRemaining[1][1] = blackTimeRemaining;
10887     DrawPosition(FALSE, boards[currentMove]);
10888
10889     return TRUE;
10890 }
10891
10892
10893 void
10894 CopyPlayerNameIntoFileName(dest, src)
10895      char **dest, *src;
10896 {
10897     while (*src != NULLCHAR && *src != ',') {
10898         if (*src == ' ') {
10899             *(*dest)++ = '_';
10900             src++;
10901         } else {
10902             *(*dest)++ = *src++;
10903         }
10904     }
10905 }
10906
10907 char *DefaultFileName(ext)
10908      char *ext;
10909 {
10910     static char def[MSG_SIZ];
10911     char *p;
10912
10913     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10914         p = def;
10915         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10916         *p++ = '-';
10917         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10918         *p++ = '.';
10919         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10920     } else {
10921         def[0] = NULLCHAR;
10922     }
10923     return def;
10924 }
10925
10926 /* Save the current game to the given file */
10927 int
10928 SaveGameToFile(filename, append)
10929      char *filename;
10930      int append;
10931 {
10932     FILE *f;
10933     char buf[MSG_SIZ];
10934
10935     if (strcmp(filename, "-") == 0) {
10936         return SaveGame(stdout, 0, NULL);
10937     } else {
10938         f = fopen(filename, append ? "a" : "w");
10939         if (f == NULL) {
10940             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10941             DisplayError(buf, errno);
10942             return FALSE;
10943         } else {
10944             return SaveGame(f, 0, NULL);
10945         }
10946     }
10947 }
10948
10949 char *
10950 SavePart(str)
10951      char *str;
10952 {
10953     static char buf[MSG_SIZ];
10954     char *p;
10955
10956     p = strchr(str, ' ');
10957     if (p == NULL) return str;
10958     strncpy(buf, str, p - str);
10959     buf[p - str] = NULLCHAR;
10960     return buf;
10961 }
10962
10963 #define PGN_MAX_LINE 75
10964
10965 #define PGN_SIDE_WHITE  0
10966 #define PGN_SIDE_BLACK  1
10967
10968 /* [AS] */
10969 static int FindFirstMoveOutOfBook( int side )
10970 {
10971     int result = -1;
10972
10973     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10974         int index = backwardMostMove;
10975         int has_book_hit = 0;
10976
10977         if( (index % 2) != side ) {
10978             index++;
10979         }
10980
10981         while( index < forwardMostMove ) {
10982             /* Check to see if engine is in book */
10983             int depth = pvInfoList[index].depth;
10984             int score = pvInfoList[index].score;
10985             int in_book = 0;
10986
10987             if( depth <= 2 ) {
10988                 in_book = 1;
10989             }
10990             else if( score == 0 && depth == 63 ) {
10991                 in_book = 1; /* Zappa */
10992             }
10993             else if( score == 2 && depth == 99 ) {
10994                 in_book = 1; /* Abrok */
10995             }
10996
10997             has_book_hit += in_book;
10998
10999             if( ! in_book ) {
11000                 result = index;
11001
11002                 break;
11003             }
11004
11005             index += 2;
11006         }
11007     }
11008
11009     return result;
11010 }
11011
11012 /* [AS] */
11013 void GetOutOfBookInfo( char * buf )
11014 {
11015     int oob[2];
11016     int i;
11017     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11018
11019     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11020     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11021
11022     *buf = '\0';
11023
11024     if( oob[0] >= 0 || oob[1] >= 0 ) {
11025         for( i=0; i<2; i++ ) {
11026             int idx = oob[i];
11027
11028             if( idx >= 0 ) {
11029                 if( i > 0 && oob[0] >= 0 ) {
11030                     strcat( buf, "   " );
11031                 }
11032
11033                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11034                 sprintf( buf+strlen(buf), "%s%.2f",
11035                     pvInfoList[idx].score >= 0 ? "+" : "",
11036                     pvInfoList[idx].score / 100.0 );
11037             }
11038         }
11039     }
11040 }
11041
11042 /* Save game in PGN style and close the file */
11043 int
11044 SaveGamePGN(f)
11045      FILE *f;
11046 {
11047     int i, offset, linelen, newblock;
11048     time_t tm;
11049 //    char *movetext;
11050     char numtext[32];
11051     int movelen, numlen, blank;
11052     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11053
11054     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11055
11056     tm = time((time_t *) NULL);
11057
11058     PrintPGNTags(f, &gameInfo);
11059
11060     if (backwardMostMove > 0 || startedFromSetupPosition) {
11061         char *fen = PositionToFEN(backwardMostMove, NULL);
11062         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11063         fprintf(f, "\n{--------------\n");
11064         PrintPosition(f, backwardMostMove);
11065         fprintf(f, "--------------}\n");
11066         free(fen);
11067     }
11068     else {
11069         /* [AS] Out of book annotation */
11070         if( appData.saveOutOfBookInfo ) {
11071             char buf[64];
11072
11073             GetOutOfBookInfo( buf );
11074
11075             if( buf[0] != '\0' ) {
11076                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11077             }
11078         }
11079
11080         fprintf(f, "\n");
11081     }
11082
11083     i = backwardMostMove;
11084     linelen = 0;
11085     newblock = TRUE;
11086
11087     while (i < forwardMostMove) {
11088         /* Print comments preceding this move */
11089         if (commentList[i] != NULL) {
11090             if (linelen > 0) fprintf(f, "\n");
11091             fprintf(f, "%s", commentList[i]);
11092             linelen = 0;
11093             newblock = TRUE;
11094         }
11095
11096         /* Format move number */
11097         if ((i % 2) == 0)
11098           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11099         else
11100           if (newblock)
11101             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11102           else
11103             numtext[0] = NULLCHAR;
11104
11105         numlen = strlen(numtext);
11106         newblock = FALSE;
11107
11108         /* Print move number */
11109         blank = linelen > 0 && numlen > 0;
11110         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11111             fprintf(f, "\n");
11112             linelen = 0;
11113             blank = 0;
11114         }
11115         if (blank) {
11116             fprintf(f, " ");
11117             linelen++;
11118         }
11119         fprintf(f, "%s", numtext);
11120         linelen += numlen;
11121
11122         /* Get move */
11123         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11124         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11125
11126         /* Print move */
11127         blank = linelen > 0 && movelen > 0;
11128         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11129             fprintf(f, "\n");
11130             linelen = 0;
11131             blank = 0;
11132         }
11133         if (blank) {
11134             fprintf(f, " ");
11135             linelen++;
11136         }
11137         fprintf(f, "%s", move_buffer);
11138         linelen += movelen;
11139
11140         /* [AS] Add PV info if present */
11141         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11142             /* [HGM] add time */
11143             char buf[MSG_SIZ]; int seconds;
11144
11145             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11146
11147             if( seconds <= 0)
11148               buf[0] = 0;
11149             else
11150               if( seconds < 30 )
11151                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11152               else
11153                 {
11154                   seconds = (seconds + 4)/10; // round to full seconds
11155                   if( seconds < 60 )
11156                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11157                   else
11158                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11159                 }
11160
11161             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11162                       pvInfoList[i].score >= 0 ? "+" : "",
11163                       pvInfoList[i].score / 100.0,
11164                       pvInfoList[i].depth,
11165                       buf );
11166
11167             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11168
11169             /* Print score/depth */
11170             blank = linelen > 0 && movelen > 0;
11171             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11172                 fprintf(f, "\n");
11173                 linelen = 0;
11174                 blank = 0;
11175             }
11176             if (blank) {
11177                 fprintf(f, " ");
11178                 linelen++;
11179             }
11180             fprintf(f, "%s", move_buffer);
11181             linelen += movelen;
11182         }
11183
11184         i++;
11185     }
11186
11187     /* Start a new line */
11188     if (linelen > 0) fprintf(f, "\n");
11189
11190     /* Print comments after last move */
11191     if (commentList[i] != NULL) {
11192         fprintf(f, "%s\n", commentList[i]);
11193     }
11194
11195     /* Print result */
11196     if (gameInfo.resultDetails != NULL &&
11197         gameInfo.resultDetails[0] != NULLCHAR) {
11198         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11199                 PGNResult(gameInfo.result));
11200     } else {
11201         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11202     }
11203
11204     fclose(f);
11205     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11206     return TRUE;
11207 }
11208
11209 /* Save game in old style and close the file */
11210 int
11211 SaveGameOldStyle(f)
11212      FILE *f;
11213 {
11214     int i, offset;
11215     time_t tm;
11216
11217     tm = time((time_t *) NULL);
11218
11219     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11220     PrintOpponents(f);
11221
11222     if (backwardMostMove > 0 || startedFromSetupPosition) {
11223         fprintf(f, "\n[--------------\n");
11224         PrintPosition(f, backwardMostMove);
11225         fprintf(f, "--------------]\n");
11226     } else {
11227         fprintf(f, "\n");
11228     }
11229
11230     i = backwardMostMove;
11231     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11232
11233     while (i < forwardMostMove) {
11234         if (commentList[i] != NULL) {
11235             fprintf(f, "[%s]\n", commentList[i]);
11236         }
11237
11238         if ((i % 2) == 1) {
11239             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11240             i++;
11241         } else {
11242             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11243             i++;
11244             if (commentList[i] != NULL) {
11245                 fprintf(f, "\n");
11246                 continue;
11247             }
11248             if (i >= forwardMostMove) {
11249                 fprintf(f, "\n");
11250                 break;
11251             }
11252             fprintf(f, "%s\n", parseList[i]);
11253             i++;
11254         }
11255     }
11256
11257     if (commentList[i] != NULL) {
11258         fprintf(f, "[%s]\n", commentList[i]);
11259     }
11260
11261     /* This isn't really the old style, but it's close enough */
11262     if (gameInfo.resultDetails != NULL &&
11263         gameInfo.resultDetails[0] != NULLCHAR) {
11264         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11265                 gameInfo.resultDetails);
11266     } else {
11267         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11268     }
11269
11270     fclose(f);
11271     return TRUE;
11272 }
11273
11274 /* Save the current game to open file f and close the file */
11275 int
11276 SaveGame(f, dummy, dummy2)
11277      FILE *f;
11278      int dummy;
11279      char *dummy2;
11280 {
11281     if (gameMode == EditPosition) EditPositionDone(TRUE);
11282     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11283     if (appData.oldSaveStyle)
11284       return SaveGameOldStyle(f);
11285     else
11286       return SaveGamePGN(f);
11287 }
11288
11289 /* Save the current position to the given file */
11290 int
11291 SavePositionToFile(filename)
11292      char *filename;
11293 {
11294     FILE *f;
11295     char buf[MSG_SIZ];
11296
11297     if (strcmp(filename, "-") == 0) {
11298         return SavePosition(stdout, 0, NULL);
11299     } else {
11300         f = fopen(filename, "a");
11301         if (f == NULL) {
11302             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11303             DisplayError(buf, errno);
11304             return FALSE;
11305         } else {
11306             SavePosition(f, 0, NULL);
11307             return TRUE;
11308         }
11309     }
11310 }
11311
11312 /* Save the current position to the given open file and close the file */
11313 int
11314 SavePosition(f, dummy, dummy2)
11315      FILE *f;
11316      int dummy;
11317      char *dummy2;
11318 {
11319     time_t tm;
11320     char *fen;
11321
11322     if (gameMode == EditPosition) EditPositionDone(TRUE);
11323     if (appData.oldSaveStyle) {
11324         tm = time((time_t *) NULL);
11325
11326         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11327         PrintOpponents(f);
11328         fprintf(f, "[--------------\n");
11329         PrintPosition(f, currentMove);
11330         fprintf(f, "--------------]\n");
11331     } else {
11332         fen = PositionToFEN(currentMove, NULL);
11333         fprintf(f, "%s\n", fen);
11334         free(fen);
11335     }
11336     fclose(f);
11337     return TRUE;
11338 }
11339
11340 void
11341 ReloadCmailMsgEvent(unregister)
11342      int unregister;
11343 {
11344 #if !WIN32
11345     static char *inFilename = NULL;
11346     static char *outFilename;
11347     int i;
11348     struct stat inbuf, outbuf;
11349     int status;
11350
11351     /* Any registered moves are unregistered if unregister is set, */
11352     /* i.e. invoked by the signal handler */
11353     if (unregister) {
11354         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11355             cmailMoveRegistered[i] = FALSE;
11356             if (cmailCommentList[i] != NULL) {
11357                 free(cmailCommentList[i]);
11358                 cmailCommentList[i] = NULL;
11359             }
11360         }
11361         nCmailMovesRegistered = 0;
11362     }
11363
11364     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11365         cmailResult[i] = CMAIL_NOT_RESULT;
11366     }
11367     nCmailResults = 0;
11368
11369     if (inFilename == NULL) {
11370         /* Because the filenames are static they only get malloced once  */
11371         /* and they never get freed                                      */
11372         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11373         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11374
11375         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11376         sprintf(outFilename, "%s.out", appData.cmailGameName);
11377     }
11378
11379     status = stat(outFilename, &outbuf);
11380     if (status < 0) {
11381         cmailMailedMove = FALSE;
11382     } else {
11383         status = stat(inFilename, &inbuf);
11384         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11385     }
11386
11387     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11388        counts the games, notes how each one terminated, etc.
11389
11390        It would be nice to remove this kludge and instead gather all
11391        the information while building the game list.  (And to keep it
11392        in the game list nodes instead of having a bunch of fixed-size
11393        parallel arrays.)  Note this will require getting each game's
11394        termination from the PGN tags, as the game list builder does
11395        not process the game moves.  --mann
11396        */
11397     cmailMsgLoaded = TRUE;
11398     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11399
11400     /* Load first game in the file or popup game menu */
11401     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11402
11403 #endif /* !WIN32 */
11404     return;
11405 }
11406
11407 int
11408 RegisterMove()
11409 {
11410     FILE *f;
11411     char string[MSG_SIZ];
11412
11413     if (   cmailMailedMove
11414         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11415         return TRUE;            /* Allow free viewing  */
11416     }
11417
11418     /* Unregister move to ensure that we don't leave RegisterMove        */
11419     /* with the move registered when the conditions for registering no   */
11420     /* longer hold                                                       */
11421     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11422         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11423         nCmailMovesRegistered --;
11424
11425         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11426           {
11427               free(cmailCommentList[lastLoadGameNumber - 1]);
11428               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11429           }
11430     }
11431
11432     if (cmailOldMove == -1) {
11433         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11434         return FALSE;
11435     }
11436
11437     if (currentMove > cmailOldMove + 1) {
11438         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11439         return FALSE;
11440     }
11441
11442     if (currentMove < cmailOldMove) {
11443         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11444         return FALSE;
11445     }
11446
11447     if (forwardMostMove > currentMove) {
11448         /* Silently truncate extra moves */
11449         TruncateGame();
11450     }
11451
11452     if (   (currentMove == cmailOldMove + 1)
11453         || (   (currentMove == cmailOldMove)
11454             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11455                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11456         if (gameInfo.result != GameUnfinished) {
11457             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11458         }
11459
11460         if (commentList[currentMove] != NULL) {
11461             cmailCommentList[lastLoadGameNumber - 1]
11462               = StrSave(commentList[currentMove]);
11463         }
11464         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11465
11466         if (appData.debugMode)
11467           fprintf(debugFP, "Saving %s for game %d\n",
11468                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11469
11470         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11471
11472         f = fopen(string, "w");
11473         if (appData.oldSaveStyle) {
11474             SaveGameOldStyle(f); /* also closes the file */
11475
11476             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11477             f = fopen(string, "w");
11478             SavePosition(f, 0, NULL); /* also closes the file */
11479         } else {
11480             fprintf(f, "{--------------\n");
11481             PrintPosition(f, currentMove);
11482             fprintf(f, "--------------}\n\n");
11483
11484             SaveGame(f, 0, NULL); /* also closes the file*/
11485         }
11486
11487         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11488         nCmailMovesRegistered ++;
11489     } else if (nCmailGames == 1) {
11490         DisplayError(_("You have not made a move yet"), 0);
11491         return FALSE;
11492     }
11493
11494     return TRUE;
11495 }
11496
11497 void
11498 MailMoveEvent()
11499 {
11500 #if !WIN32
11501     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11502     FILE *commandOutput;
11503     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11504     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11505     int nBuffers;
11506     int i;
11507     int archived;
11508     char *arcDir;
11509
11510     if (! cmailMsgLoaded) {
11511         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11512         return;
11513     }
11514
11515     if (nCmailGames == nCmailResults) {
11516         DisplayError(_("No unfinished games"), 0);
11517         return;
11518     }
11519
11520 #if CMAIL_PROHIBIT_REMAIL
11521     if (cmailMailedMove) {
11522       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);
11523         DisplayError(msg, 0);
11524         return;
11525     }
11526 #endif
11527
11528     if (! (cmailMailedMove || RegisterMove())) return;
11529
11530     if (   cmailMailedMove
11531         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11532       snprintf(string, MSG_SIZ, partCommandString,
11533                appData.debugMode ? " -v" : "", appData.cmailGameName);
11534         commandOutput = popen(string, "r");
11535
11536         if (commandOutput == NULL) {
11537             DisplayError(_("Failed to invoke cmail"), 0);
11538         } else {
11539             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11540                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11541             }
11542             if (nBuffers > 1) {
11543                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11544                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11545                 nBytes = MSG_SIZ - 1;
11546             } else {
11547                 (void) memcpy(msg, buffer, nBytes);
11548             }
11549             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11550
11551             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11552                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11553
11554                 archived = TRUE;
11555                 for (i = 0; i < nCmailGames; i ++) {
11556                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11557                         archived = FALSE;
11558                     }
11559                 }
11560                 if (   archived
11561                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11562                         != NULL)) {
11563                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11564                            arcDir,
11565                            appData.cmailGameName,
11566                            gameInfo.date);
11567                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11568                     cmailMsgLoaded = FALSE;
11569                 }
11570             }
11571
11572             DisplayInformation(msg);
11573             pclose(commandOutput);
11574         }
11575     } else {
11576         if ((*cmailMsg) != '\0') {
11577             DisplayInformation(cmailMsg);
11578         }
11579     }
11580
11581     return;
11582 #endif /* !WIN32 */
11583 }
11584
11585 char *
11586 CmailMsg()
11587 {
11588 #if WIN32
11589     return NULL;
11590 #else
11591     int  prependComma = 0;
11592     char number[5];
11593     char string[MSG_SIZ];       /* Space for game-list */
11594     int  i;
11595
11596     if (!cmailMsgLoaded) return "";
11597
11598     if (cmailMailedMove) {
11599       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11600     } else {
11601         /* Create a list of games left */
11602       snprintf(string, MSG_SIZ, "[");
11603         for (i = 0; i < nCmailGames; i ++) {
11604             if (! (   cmailMoveRegistered[i]
11605                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11606                 if (prependComma) {
11607                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11608                 } else {
11609                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11610                     prependComma = 1;
11611                 }
11612
11613                 strcat(string, number);
11614             }
11615         }
11616         strcat(string, "]");
11617
11618         if (nCmailMovesRegistered + nCmailResults == 0) {
11619             switch (nCmailGames) {
11620               case 1:
11621                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11622                 break;
11623
11624               case 2:
11625                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11626                 break;
11627
11628               default:
11629                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11630                          nCmailGames);
11631                 break;
11632             }
11633         } else {
11634             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11635               case 1:
11636                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11637                          string);
11638                 break;
11639
11640               case 0:
11641                 if (nCmailResults == nCmailGames) {
11642                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11643                 } else {
11644                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11645                 }
11646                 break;
11647
11648               default:
11649                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11650                          string);
11651             }
11652         }
11653     }
11654     return cmailMsg;
11655 #endif /* WIN32 */
11656 }
11657
11658 void
11659 ResetGameEvent()
11660 {
11661     if (gameMode == Training)
11662       SetTrainingModeOff();
11663
11664     Reset(TRUE, TRUE);
11665     cmailMsgLoaded = FALSE;
11666     if (appData.icsActive) {
11667       SendToICS(ics_prefix);
11668       SendToICS("refresh\n");
11669     }
11670 }
11671
11672 void
11673 ExitEvent(status)
11674      int status;
11675 {
11676     exiting++;
11677     if (exiting > 2) {
11678       /* Give up on clean exit */
11679       exit(status);
11680     }
11681     if (exiting > 1) {
11682       /* Keep trying for clean exit */
11683       return;
11684     }
11685
11686     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11687
11688     if (telnetISR != NULL) {
11689       RemoveInputSource(telnetISR);
11690     }
11691     if (icsPR != NoProc) {
11692       DestroyChildProcess(icsPR, TRUE);
11693     }
11694
11695     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11696     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11697
11698     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11699     /* make sure this other one finishes before killing it!                  */
11700     if(endingGame) { int count = 0;
11701         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11702         while(endingGame && count++ < 10) DoSleep(1);
11703         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11704     }
11705
11706     /* Kill off chess programs */
11707     if (first.pr != NoProc) {
11708         ExitAnalyzeMode();
11709
11710         DoSleep( appData.delayBeforeQuit );
11711         SendToProgram("quit\n", &first);
11712         DoSleep( appData.delayAfterQuit );
11713         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11714     }
11715     if (second.pr != NoProc) {
11716         DoSleep( appData.delayBeforeQuit );
11717         SendToProgram("quit\n", &second);
11718         DoSleep( appData.delayAfterQuit );
11719         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11720     }
11721     if (first.isr != NULL) {
11722         RemoveInputSource(first.isr);
11723     }
11724     if (second.isr != NULL) {
11725         RemoveInputSource(second.isr);
11726     }
11727
11728     ShutDownFrontEnd();
11729     exit(status);
11730 }
11731
11732 void
11733 PauseEvent()
11734 {
11735     if (appData.debugMode)
11736         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11737     if (pausing) {
11738         pausing = FALSE;
11739         ModeHighlight();
11740         if (gameMode == MachinePlaysWhite ||
11741             gameMode == MachinePlaysBlack) {
11742             StartClocks();
11743         } else {
11744             DisplayBothClocks();
11745         }
11746         if (gameMode == PlayFromGameFile) {
11747             if (appData.timeDelay >= 0)
11748                 AutoPlayGameLoop();
11749         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11750             Reset(FALSE, TRUE);
11751             SendToICS(ics_prefix);
11752             SendToICS("refresh\n");
11753         } else if (currentMove < forwardMostMove) {
11754             ForwardInner(forwardMostMove);
11755         }
11756         pauseExamInvalid = FALSE;
11757     } else {
11758         switch (gameMode) {
11759           default:
11760             return;
11761           case IcsExamining:
11762             pauseExamForwardMostMove = forwardMostMove;
11763             pauseExamInvalid = FALSE;
11764             /* fall through */
11765           case IcsObserving:
11766           case IcsPlayingWhite:
11767           case IcsPlayingBlack:
11768             pausing = TRUE;
11769             ModeHighlight();
11770             return;
11771           case PlayFromGameFile:
11772             (void) StopLoadGameTimer();
11773             pausing = TRUE;
11774             ModeHighlight();
11775             break;
11776           case BeginningOfGame:
11777             if (appData.icsActive) return;
11778             /* else fall through */
11779           case MachinePlaysWhite:
11780           case MachinePlaysBlack:
11781           case TwoMachinesPlay:
11782             if (forwardMostMove == 0)
11783               return;           /* don't pause if no one has moved */
11784             if ((gameMode == MachinePlaysWhite &&
11785                  !WhiteOnMove(forwardMostMove)) ||
11786                 (gameMode == MachinePlaysBlack &&
11787                  WhiteOnMove(forwardMostMove))) {
11788                 StopClocks();
11789             }
11790             pausing = TRUE;
11791             ModeHighlight();
11792             break;
11793         }
11794     }
11795 }
11796
11797 void
11798 EditCommentEvent()
11799 {
11800     char title[MSG_SIZ];
11801
11802     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11803       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11804     } else {
11805       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11806                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11807                parseList[currentMove - 1]);
11808     }
11809
11810     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11811 }
11812
11813
11814 void
11815 EditTagsEvent()
11816 {
11817     char *tags = PGNTags(&gameInfo);
11818     EditTagsPopUp(tags, NULL);
11819     free(tags);
11820 }
11821
11822 void
11823 AnalyzeModeEvent()
11824 {
11825     if (appData.noChessProgram || gameMode == AnalyzeMode)
11826       return;
11827
11828     if (gameMode != AnalyzeFile) {
11829         if (!appData.icsEngineAnalyze) {
11830                EditGameEvent();
11831                if (gameMode != EditGame) return;
11832         }
11833         ResurrectChessProgram();
11834         SendToProgram("analyze\n", &first);
11835         first.analyzing = TRUE;
11836         /*first.maybeThinking = TRUE;*/
11837         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11838         EngineOutputPopUp();
11839     }
11840     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11841     pausing = FALSE;
11842     ModeHighlight();
11843     SetGameInfo();
11844
11845     StartAnalysisClock();
11846     GetTimeMark(&lastNodeCountTime);
11847     lastNodeCount = 0;
11848 }
11849
11850 void
11851 AnalyzeFileEvent()
11852 {
11853     if (appData.noChessProgram || gameMode == AnalyzeFile)
11854       return;
11855
11856     if (gameMode != AnalyzeMode) {
11857         EditGameEvent();
11858         if (gameMode != EditGame) return;
11859         ResurrectChessProgram();
11860         SendToProgram("analyze\n", &first);
11861         first.analyzing = TRUE;
11862         /*first.maybeThinking = TRUE;*/
11863         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11864         EngineOutputPopUp();
11865     }
11866     gameMode = AnalyzeFile;
11867     pausing = FALSE;
11868     ModeHighlight();
11869     SetGameInfo();
11870
11871     StartAnalysisClock();
11872     GetTimeMark(&lastNodeCountTime);
11873     lastNodeCount = 0;
11874 }
11875
11876 void
11877 MachineWhiteEvent()
11878 {
11879     char buf[MSG_SIZ];
11880     char *bookHit = NULL;
11881
11882     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11883       return;
11884
11885
11886     if (gameMode == PlayFromGameFile ||
11887         gameMode == TwoMachinesPlay  ||
11888         gameMode == Training         ||
11889         gameMode == AnalyzeMode      ||
11890         gameMode == EndOfGame)
11891         EditGameEvent();
11892
11893     if (gameMode == EditPosition)
11894         EditPositionDone(TRUE);
11895
11896     if (!WhiteOnMove(currentMove)) {
11897         DisplayError(_("It is not White's turn"), 0);
11898         return;
11899     }
11900
11901     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11902       ExitAnalyzeMode();
11903
11904     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11905         gameMode == AnalyzeFile)
11906         TruncateGame();
11907
11908     ResurrectChessProgram();    /* in case it isn't running */
11909     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11910         gameMode = MachinePlaysWhite;
11911         ResetClocks();
11912     } else
11913     gameMode = MachinePlaysWhite;
11914     pausing = FALSE;
11915     ModeHighlight();
11916     SetGameInfo();
11917     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11918     DisplayTitle(buf);
11919     if (first.sendName) {
11920       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11921       SendToProgram(buf, &first);
11922     }
11923     if (first.sendTime) {
11924       if (first.useColors) {
11925         SendToProgram("black\n", &first); /*gnu kludge*/
11926       }
11927       SendTimeRemaining(&first, TRUE);
11928     }
11929     if (first.useColors) {
11930       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11931     }
11932     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11933     SetMachineThinkingEnables();
11934     first.maybeThinking = TRUE;
11935     StartClocks();
11936     firstMove = FALSE;
11937
11938     if (appData.autoFlipView && !flipView) {
11939       flipView = !flipView;
11940       DrawPosition(FALSE, NULL);
11941       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11942     }
11943
11944     if(bookHit) { // [HGM] book: simulate book reply
11945         static char bookMove[MSG_SIZ]; // a bit generous?
11946
11947         programStats.nodes = programStats.depth = programStats.time =
11948         programStats.score = programStats.got_only_move = 0;
11949         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11950
11951         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11952         strcat(bookMove, bookHit);
11953         HandleMachineMove(bookMove, &first);
11954     }
11955 }
11956
11957 void
11958 MachineBlackEvent()
11959 {
11960   char buf[MSG_SIZ];
11961   char *bookHit = NULL;
11962
11963     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11964         return;
11965
11966
11967     if (gameMode == PlayFromGameFile ||
11968         gameMode == TwoMachinesPlay  ||
11969         gameMode == Training         ||
11970         gameMode == AnalyzeMode      ||
11971         gameMode == EndOfGame)
11972         EditGameEvent();
11973
11974     if (gameMode == EditPosition)
11975         EditPositionDone(TRUE);
11976
11977     if (WhiteOnMove(currentMove)) {
11978         DisplayError(_("It is not Black's turn"), 0);
11979         return;
11980     }
11981
11982     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11983       ExitAnalyzeMode();
11984
11985     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11986         gameMode == AnalyzeFile)
11987         TruncateGame();
11988
11989     ResurrectChessProgram();    /* in case it isn't running */
11990     gameMode = MachinePlaysBlack;
11991     pausing = FALSE;
11992     ModeHighlight();
11993     SetGameInfo();
11994     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11995     DisplayTitle(buf);
11996     if (first.sendName) {
11997       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11998       SendToProgram(buf, &first);
11999     }
12000     if (first.sendTime) {
12001       if (first.useColors) {
12002         SendToProgram("white\n", &first); /*gnu kludge*/
12003       }
12004       SendTimeRemaining(&first, FALSE);
12005     }
12006     if (first.useColors) {
12007       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12008     }
12009     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12010     SetMachineThinkingEnables();
12011     first.maybeThinking = TRUE;
12012     StartClocks();
12013
12014     if (appData.autoFlipView && flipView) {
12015       flipView = !flipView;
12016       DrawPosition(FALSE, NULL);
12017       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12018     }
12019     if(bookHit) { // [HGM] book: simulate book reply
12020         static char bookMove[MSG_SIZ]; // a bit generous?
12021
12022         programStats.nodes = programStats.depth = programStats.time =
12023         programStats.score = programStats.got_only_move = 0;
12024         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12025
12026         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12027         strcat(bookMove, bookHit);
12028         HandleMachineMove(bookMove, &first);
12029     }
12030 }
12031
12032
12033 void
12034 DisplayTwoMachinesTitle()
12035 {
12036     char buf[MSG_SIZ];
12037     if (appData.matchGames > 0) {
12038         if (first.twoMachinesColor[0] == 'w') {
12039           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12040                    gameInfo.white, gameInfo.black,
12041                    first.matchWins, second.matchWins,
12042                    matchGame - 1 - (first.matchWins + second.matchWins));
12043         } else {
12044           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12045                    gameInfo.white, gameInfo.black,
12046                    second.matchWins, first.matchWins,
12047                    matchGame - 1 - (first.matchWins + second.matchWins));
12048         }
12049     } else {
12050       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12051     }
12052     DisplayTitle(buf);
12053 }
12054
12055 void
12056 SettingsMenuIfReady()
12057 {
12058   if (second.lastPing != second.lastPong) {
12059     DisplayMessage("", _("Waiting for second chess program"));
12060     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12061     return;
12062   }
12063   ThawUI();
12064   DisplayMessage("", "");
12065   SettingsPopUp(&second);
12066 }
12067
12068 int
12069 WaitForSecond(DelayedEventCallback retry)
12070 {
12071     if (second.pr == NULL) {
12072         StartChessProgram(&second);
12073         if (second.protocolVersion == 1) {
12074           retry();
12075         } else {
12076           /* kludge: allow timeout for initial "feature" command */
12077           FreezeUI();
12078           DisplayMessage("", _("Starting second chess program"));
12079           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12080         }
12081         return 1;
12082     }
12083     return 0;
12084 }
12085
12086 void
12087 TwoMachinesEvent P((void))
12088 {
12089     int i;
12090     char buf[MSG_SIZ];
12091     ChessProgramState *onmove;
12092     char *bookHit = NULL;
12093
12094     if (appData.noChessProgram) return;
12095
12096     switch (gameMode) {
12097       case TwoMachinesPlay:
12098         return;
12099       case MachinePlaysWhite:
12100       case MachinePlaysBlack:
12101         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12102             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12103             return;
12104         }
12105         /* fall through */
12106       case BeginningOfGame:
12107       case PlayFromGameFile:
12108       case EndOfGame:
12109         EditGameEvent();
12110         if (gameMode != EditGame) return;
12111         break;
12112       case EditPosition:
12113         EditPositionDone(TRUE);
12114         break;
12115       case AnalyzeMode:
12116       case AnalyzeFile:
12117         ExitAnalyzeMode();
12118         break;
12119       case EditGame:
12120       default:
12121         break;
12122     }
12123
12124 //    forwardMostMove = currentMove;
12125     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12126     ResurrectChessProgram();    /* in case first program isn't running */
12127
12128     if(WaitForSecond(TwoMachinesEventIfReady)) return;
12129     DisplayMessage("", "");
12130     InitChessProgram(&second, FALSE);
12131     SendToProgram("force\n", &second);
12132     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12133       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12134       return;
12135     }
12136     if (startedFromSetupPosition) {
12137         SendBoard(&second, backwardMostMove);
12138     if (appData.debugMode) {
12139         fprintf(debugFP, "Two Machines\n");
12140     }
12141     }
12142     for (i = backwardMostMove; i < forwardMostMove; i++) {
12143         SendMoveToProgram(i, &second);
12144     }
12145
12146     gameMode = TwoMachinesPlay;
12147     pausing = FALSE;
12148     ModeHighlight();
12149     SetGameInfo();
12150     DisplayTwoMachinesTitle();
12151     firstMove = TRUE;
12152     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12153         onmove = &first;
12154     } else {
12155         onmove = &second;
12156     }
12157
12158     SendToProgram(first.computerString, &first);
12159     if (first.sendName) {
12160       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12161       SendToProgram(buf, &first);
12162     }
12163     SendToProgram(second.computerString, &second);
12164     if (second.sendName) {
12165       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12166       SendToProgram(buf, &second);
12167     }
12168
12169     ResetClocks();
12170     if (!first.sendTime || !second.sendTime) {
12171         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12172         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12173     }
12174     if (onmove->sendTime) {
12175       if (onmove->useColors) {
12176         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12177       }
12178       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12179     }
12180     if (onmove->useColors) {
12181       SendToProgram(onmove->twoMachinesColor, onmove);
12182     }
12183     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12184 //    SendToProgram("go\n", onmove);
12185     onmove->maybeThinking = TRUE;
12186     SetMachineThinkingEnables();
12187
12188     StartClocks();
12189
12190     if(bookHit) { // [HGM] book: simulate book reply
12191         static char bookMove[MSG_SIZ]; // a bit generous?
12192
12193         programStats.nodes = programStats.depth = programStats.time =
12194         programStats.score = programStats.got_only_move = 0;
12195         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12196
12197         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12198         strcat(bookMove, bookHit);
12199         savedMessage = bookMove; // args for deferred call
12200         savedState = onmove;
12201         ScheduleDelayedEvent(DeferredBookMove, 1);
12202     }
12203 }
12204
12205 void
12206 TrainingEvent()
12207 {
12208     if (gameMode == Training) {
12209       SetTrainingModeOff();
12210       gameMode = PlayFromGameFile;
12211       DisplayMessage("", _("Training mode off"));
12212     } else {
12213       gameMode = Training;
12214       animateTraining = appData.animate;
12215
12216       /* make sure we are not already at the end of the game */
12217       if (currentMove < forwardMostMove) {
12218         SetTrainingModeOn();
12219         DisplayMessage("", _("Training mode on"));
12220       } else {
12221         gameMode = PlayFromGameFile;
12222         DisplayError(_("Already at end of game"), 0);
12223       }
12224     }
12225     ModeHighlight();
12226 }
12227
12228 void
12229 IcsClientEvent()
12230 {
12231     if (!appData.icsActive) return;
12232     switch (gameMode) {
12233       case IcsPlayingWhite:
12234       case IcsPlayingBlack:
12235       case IcsObserving:
12236       case IcsIdle:
12237       case BeginningOfGame:
12238       case IcsExamining:
12239         return;
12240
12241       case EditGame:
12242         break;
12243
12244       case EditPosition:
12245         EditPositionDone(TRUE);
12246         break;
12247
12248       case AnalyzeMode:
12249       case AnalyzeFile:
12250         ExitAnalyzeMode();
12251         break;
12252
12253       default:
12254         EditGameEvent();
12255         break;
12256     }
12257
12258     gameMode = IcsIdle;
12259     ModeHighlight();
12260     return;
12261 }
12262
12263
12264 void
12265 EditGameEvent()
12266 {
12267     int i;
12268
12269     switch (gameMode) {
12270       case Training:
12271         SetTrainingModeOff();
12272         break;
12273       case MachinePlaysWhite:
12274       case MachinePlaysBlack:
12275       case BeginningOfGame:
12276         SendToProgram("force\n", &first);
12277         SetUserThinkingEnables();
12278         break;
12279       case PlayFromGameFile:
12280         (void) StopLoadGameTimer();
12281         if (gameFileFP != NULL) {
12282             gameFileFP = NULL;
12283         }
12284         break;
12285       case EditPosition:
12286         EditPositionDone(TRUE);
12287         break;
12288       case AnalyzeMode:
12289       case AnalyzeFile:
12290         ExitAnalyzeMode();
12291         SendToProgram("force\n", &first);
12292         break;
12293       case TwoMachinesPlay:
12294         GameEnds(EndOfFile, NULL, GE_PLAYER);
12295         ResurrectChessProgram();
12296         SetUserThinkingEnables();
12297         break;
12298       case EndOfGame:
12299         ResurrectChessProgram();
12300         break;
12301       case IcsPlayingBlack:
12302       case IcsPlayingWhite:
12303         DisplayError(_("Warning: You are still playing a game"), 0);
12304         break;
12305       case IcsObserving:
12306         DisplayError(_("Warning: You are still observing a game"), 0);
12307         break;
12308       case IcsExamining:
12309         DisplayError(_("Warning: You are still examining a game"), 0);
12310         break;
12311       case IcsIdle:
12312         break;
12313       case EditGame:
12314       default:
12315         return;
12316     }
12317
12318     pausing = FALSE;
12319     StopClocks();
12320     first.offeredDraw = second.offeredDraw = 0;
12321
12322     if (gameMode == PlayFromGameFile) {
12323         whiteTimeRemaining = timeRemaining[0][currentMove];
12324         blackTimeRemaining = timeRemaining[1][currentMove];
12325         DisplayTitle("");
12326     }
12327
12328     if (gameMode == MachinePlaysWhite ||
12329         gameMode == MachinePlaysBlack ||
12330         gameMode == TwoMachinesPlay ||
12331         gameMode == EndOfGame) {
12332         i = forwardMostMove;
12333         while (i > currentMove) {
12334             SendToProgram("undo\n", &first);
12335             i--;
12336         }
12337         whiteTimeRemaining = timeRemaining[0][currentMove];
12338         blackTimeRemaining = timeRemaining[1][currentMove];
12339         DisplayBothClocks();
12340         if (whiteFlag || blackFlag) {
12341             whiteFlag = blackFlag = 0;
12342         }
12343         DisplayTitle("");
12344     }
12345
12346     gameMode = EditGame;
12347     ModeHighlight();
12348     SetGameInfo();
12349 }
12350
12351
12352 void
12353 EditPositionEvent()
12354 {
12355     if (gameMode == EditPosition) {
12356         EditGameEvent();
12357         return;
12358     }
12359
12360     EditGameEvent();
12361     if (gameMode != EditGame) return;
12362
12363     gameMode = EditPosition;
12364     ModeHighlight();
12365     SetGameInfo();
12366     if (currentMove > 0)
12367       CopyBoard(boards[0], boards[currentMove]);
12368
12369     blackPlaysFirst = !WhiteOnMove(currentMove);
12370     ResetClocks();
12371     currentMove = forwardMostMove = backwardMostMove = 0;
12372     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12373     DisplayMove(-1);
12374 }
12375
12376 void
12377 ExitAnalyzeMode()
12378 {
12379     /* [DM] icsEngineAnalyze - possible call from other functions */
12380     if (appData.icsEngineAnalyze) {
12381         appData.icsEngineAnalyze = FALSE;
12382
12383         DisplayMessage("",_("Close ICS engine analyze..."));
12384     }
12385     if (first.analysisSupport && first.analyzing) {
12386       SendToProgram("exit\n", &first);
12387       first.analyzing = FALSE;
12388     }
12389     thinkOutput[0] = NULLCHAR;
12390 }
12391
12392 void
12393 EditPositionDone(Boolean fakeRights)
12394 {
12395     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12396
12397     startedFromSetupPosition = TRUE;
12398     InitChessProgram(&first, FALSE);
12399     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12400       boards[0][EP_STATUS] = EP_NONE;
12401       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12402     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12403         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12404         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12405       } else boards[0][CASTLING][2] = NoRights;
12406     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12407         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12408         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12409       } else boards[0][CASTLING][5] = NoRights;
12410     }
12411     SendToProgram("force\n", &first);
12412     if (blackPlaysFirst) {
12413         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12414         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12415         currentMove = forwardMostMove = backwardMostMove = 1;
12416         CopyBoard(boards[1], boards[0]);
12417     } else {
12418         currentMove = forwardMostMove = backwardMostMove = 0;
12419     }
12420     SendBoard(&first, forwardMostMove);
12421     if (appData.debugMode) {
12422         fprintf(debugFP, "EditPosDone\n");
12423     }
12424     DisplayTitle("");
12425     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12426     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12427     gameMode = EditGame;
12428     ModeHighlight();
12429     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12430     ClearHighlights(); /* [AS] */
12431 }
12432
12433 /* Pause for `ms' milliseconds */
12434 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12435 void
12436 TimeDelay(ms)
12437      long ms;
12438 {
12439     TimeMark m1, m2;
12440
12441     GetTimeMark(&m1);
12442     do {
12443         GetTimeMark(&m2);
12444     } while (SubtractTimeMarks(&m2, &m1) < ms);
12445 }
12446
12447 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12448 void
12449 SendMultiLineToICS(buf)
12450      char *buf;
12451 {
12452     char temp[MSG_SIZ+1], *p;
12453     int len;
12454
12455     len = strlen(buf);
12456     if (len > MSG_SIZ)
12457       len = MSG_SIZ;
12458
12459     strncpy(temp, buf, len);
12460     temp[len] = 0;
12461
12462     p = temp;
12463     while (*p) {
12464         if (*p == '\n' || *p == '\r')
12465           *p = ' ';
12466         ++p;
12467     }
12468
12469     strcat(temp, "\n");
12470     SendToICS(temp);
12471     SendToPlayer(temp, strlen(temp));
12472 }
12473
12474 void
12475 SetWhiteToPlayEvent()
12476 {
12477     if (gameMode == EditPosition) {
12478         blackPlaysFirst = FALSE;
12479         DisplayBothClocks();    /* works because currentMove is 0 */
12480     } else if (gameMode == IcsExamining) {
12481         SendToICS(ics_prefix);
12482         SendToICS("tomove white\n");
12483     }
12484 }
12485
12486 void
12487 SetBlackToPlayEvent()
12488 {
12489     if (gameMode == EditPosition) {
12490         blackPlaysFirst = TRUE;
12491         currentMove = 1;        /* kludge */
12492         DisplayBothClocks();
12493         currentMove = 0;
12494     } else if (gameMode == IcsExamining) {
12495         SendToICS(ics_prefix);
12496         SendToICS("tomove black\n");
12497     }
12498 }
12499
12500 void
12501 EditPositionMenuEvent(selection, x, y)
12502      ChessSquare selection;
12503      int x, y;
12504 {
12505     char buf[MSG_SIZ];
12506     ChessSquare piece = boards[0][y][x];
12507
12508     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12509
12510     switch (selection) {
12511       case ClearBoard:
12512         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12513             SendToICS(ics_prefix);
12514             SendToICS("bsetup clear\n");
12515         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12516             SendToICS(ics_prefix);
12517             SendToICS("clearboard\n");
12518         } else {
12519             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12520                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12521                 for (y = 0; y < BOARD_HEIGHT; y++) {
12522                     if (gameMode == IcsExamining) {
12523                         if (boards[currentMove][y][x] != EmptySquare) {
12524                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12525                                     AAA + x, ONE + y);
12526                             SendToICS(buf);
12527                         }
12528                     } else {
12529                         boards[0][y][x] = p;
12530                     }
12531                 }
12532             }
12533         }
12534         if (gameMode == EditPosition) {
12535             DrawPosition(FALSE, boards[0]);
12536         }
12537         break;
12538
12539       case WhitePlay:
12540         SetWhiteToPlayEvent();
12541         break;
12542
12543       case BlackPlay:
12544         SetBlackToPlayEvent();
12545         break;
12546
12547       case EmptySquare:
12548         if (gameMode == IcsExamining) {
12549             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12550             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12551             SendToICS(buf);
12552         } else {
12553             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12554                 if(x == BOARD_LEFT-2) {
12555                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12556                     boards[0][y][1] = 0;
12557                 } else
12558                 if(x == BOARD_RGHT+1) {
12559                     if(y >= gameInfo.holdingsSize) break;
12560                     boards[0][y][BOARD_WIDTH-2] = 0;
12561                 } else break;
12562             }
12563             boards[0][y][x] = EmptySquare;
12564             DrawPosition(FALSE, boards[0]);
12565         }
12566         break;
12567
12568       case PromotePiece:
12569         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12570            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12571             selection = (ChessSquare) (PROMOTED piece);
12572         } else if(piece == EmptySquare) selection = WhiteSilver;
12573         else selection = (ChessSquare)((int)piece - 1);
12574         goto defaultlabel;
12575
12576       case DemotePiece:
12577         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12578            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12579             selection = (ChessSquare) (DEMOTED piece);
12580         } else if(piece == EmptySquare) selection = BlackSilver;
12581         else selection = (ChessSquare)((int)piece + 1);
12582         goto defaultlabel;
12583
12584       case WhiteQueen:
12585       case BlackQueen:
12586         if(gameInfo.variant == VariantShatranj ||
12587            gameInfo.variant == VariantXiangqi  ||
12588            gameInfo.variant == VariantCourier  ||
12589            gameInfo.variant == VariantMakruk     )
12590             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12591         goto defaultlabel;
12592
12593       case WhiteKing:
12594       case BlackKing:
12595         if(gameInfo.variant == VariantXiangqi)
12596             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12597         if(gameInfo.variant == VariantKnightmate)
12598             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12599       default:
12600         defaultlabel:
12601         if (gameMode == IcsExamining) {
12602             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12603             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12604                      PieceToChar(selection), AAA + x, ONE + y);
12605             SendToICS(buf);
12606         } else {
12607             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12608                 int n;
12609                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12610                     n = PieceToNumber(selection - BlackPawn);
12611                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12612                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12613                     boards[0][BOARD_HEIGHT-1-n][1]++;
12614                 } else
12615                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12616                     n = PieceToNumber(selection);
12617                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12618                     boards[0][n][BOARD_WIDTH-1] = selection;
12619                     boards[0][n][BOARD_WIDTH-2]++;
12620                 }
12621             } else
12622             boards[0][y][x] = selection;
12623             DrawPosition(TRUE, boards[0]);
12624         }
12625         break;
12626     }
12627 }
12628
12629
12630 void
12631 DropMenuEvent(selection, x, y)
12632      ChessSquare selection;
12633      int x, y;
12634 {
12635     ChessMove moveType;
12636
12637     switch (gameMode) {
12638       case IcsPlayingWhite:
12639       case MachinePlaysBlack:
12640         if (!WhiteOnMove(currentMove)) {
12641             DisplayMoveError(_("It is Black's turn"));
12642             return;
12643         }
12644         moveType = WhiteDrop;
12645         break;
12646       case IcsPlayingBlack:
12647       case MachinePlaysWhite:
12648         if (WhiteOnMove(currentMove)) {
12649             DisplayMoveError(_("It is White's turn"));
12650             return;
12651         }
12652         moveType = BlackDrop;
12653         break;
12654       case EditGame:
12655         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12656         break;
12657       default:
12658         return;
12659     }
12660
12661     if (moveType == BlackDrop && selection < BlackPawn) {
12662       selection = (ChessSquare) ((int) selection
12663                                  + (int) BlackPawn - (int) WhitePawn);
12664     }
12665     if (boards[currentMove][y][x] != EmptySquare) {
12666         DisplayMoveError(_("That square is occupied"));
12667         return;
12668     }
12669
12670     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12671 }
12672
12673 void
12674 AcceptEvent()
12675 {
12676     /* Accept a pending offer of any kind from opponent */
12677
12678     if (appData.icsActive) {
12679         SendToICS(ics_prefix);
12680         SendToICS("accept\n");
12681     } else if (cmailMsgLoaded) {
12682         if (currentMove == cmailOldMove &&
12683             commentList[cmailOldMove] != NULL &&
12684             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12685                    "Black offers a draw" : "White offers a draw")) {
12686             TruncateGame();
12687             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12688             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12689         } else {
12690             DisplayError(_("There is no pending offer on this move"), 0);
12691             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12692         }
12693     } else {
12694         /* Not used for offers from chess program */
12695     }
12696 }
12697
12698 void
12699 DeclineEvent()
12700 {
12701     /* Decline a pending offer of any kind from opponent */
12702
12703     if (appData.icsActive) {
12704         SendToICS(ics_prefix);
12705         SendToICS("decline\n");
12706     } else if (cmailMsgLoaded) {
12707         if (currentMove == cmailOldMove &&
12708             commentList[cmailOldMove] != NULL &&
12709             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12710                    "Black offers a draw" : "White offers a draw")) {
12711 #ifdef NOTDEF
12712             AppendComment(cmailOldMove, "Draw declined", TRUE);
12713             DisplayComment(cmailOldMove - 1, "Draw declined");
12714 #endif /*NOTDEF*/
12715         } else {
12716             DisplayError(_("There is no pending offer on this move"), 0);
12717         }
12718     } else {
12719         /* Not used for offers from chess program */
12720     }
12721 }
12722
12723 void
12724 RematchEvent()
12725 {
12726     /* Issue ICS rematch command */
12727     if (appData.icsActive) {
12728         SendToICS(ics_prefix);
12729         SendToICS("rematch\n");
12730     }
12731 }
12732
12733 void
12734 CallFlagEvent()
12735 {
12736     /* Call your opponent's flag (claim a win on time) */
12737     if (appData.icsActive) {
12738         SendToICS(ics_prefix);
12739         SendToICS("flag\n");
12740     } else {
12741         switch (gameMode) {
12742           default:
12743             return;
12744           case MachinePlaysWhite:
12745             if (whiteFlag) {
12746                 if (blackFlag)
12747                   GameEnds(GameIsDrawn, "Both players ran out of time",
12748                            GE_PLAYER);
12749                 else
12750                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12751             } else {
12752                 DisplayError(_("Your opponent is not out of time"), 0);
12753             }
12754             break;
12755           case MachinePlaysBlack:
12756             if (blackFlag) {
12757                 if (whiteFlag)
12758                   GameEnds(GameIsDrawn, "Both players ran out of time",
12759                            GE_PLAYER);
12760                 else
12761                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12762             } else {
12763                 DisplayError(_("Your opponent is not out of time"), 0);
12764             }
12765             break;
12766         }
12767     }
12768 }
12769
12770 void
12771 ClockClick(int which)
12772 {       // [HGM] code moved to back-end from winboard.c
12773         if(which) { // black clock
12774           if (gameMode == EditPosition || gameMode == IcsExamining) {
12775             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12776             SetBlackToPlayEvent();
12777           } else if (gameMode == EditGame || shiftKey) {
12778             AdjustClock(which, -1);
12779           } else if (gameMode == IcsPlayingWhite ||
12780                      gameMode == MachinePlaysBlack) {
12781             CallFlagEvent();
12782           }
12783         } else { // white clock
12784           if (gameMode == EditPosition || gameMode == IcsExamining) {
12785             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12786             SetWhiteToPlayEvent();
12787           } else if (gameMode == EditGame || shiftKey) {
12788             AdjustClock(which, -1);
12789           } else if (gameMode == IcsPlayingBlack ||
12790                    gameMode == MachinePlaysWhite) {
12791             CallFlagEvent();
12792           }
12793         }
12794 }
12795
12796 void
12797 DrawEvent()
12798 {
12799     /* Offer draw or accept pending draw offer from opponent */
12800
12801     if (appData.icsActive) {
12802         /* Note: tournament rules require draw offers to be
12803            made after you make your move but before you punch
12804            your clock.  Currently ICS doesn't let you do that;
12805            instead, you immediately punch your clock after making
12806            a move, but you can offer a draw at any time. */
12807
12808         SendToICS(ics_prefix);
12809         SendToICS("draw\n");
12810         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12811     } else if (cmailMsgLoaded) {
12812         if (currentMove == cmailOldMove &&
12813             commentList[cmailOldMove] != NULL &&
12814             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12815                    "Black offers a draw" : "White offers a draw")) {
12816             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12817             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12818         } else if (currentMove == cmailOldMove + 1) {
12819             char *offer = WhiteOnMove(cmailOldMove) ?
12820               "White offers a draw" : "Black offers a draw";
12821             AppendComment(currentMove, offer, TRUE);
12822             DisplayComment(currentMove - 1, offer);
12823             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12824         } else {
12825             DisplayError(_("You must make your move before offering a draw"), 0);
12826             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12827         }
12828     } else if (first.offeredDraw) {
12829         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12830     } else {
12831         if (first.sendDrawOffers) {
12832             SendToProgram("draw\n", &first);
12833             userOfferedDraw = TRUE;
12834         }
12835     }
12836 }
12837
12838 void
12839 AdjournEvent()
12840 {
12841     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12842
12843     if (appData.icsActive) {
12844         SendToICS(ics_prefix);
12845         SendToICS("adjourn\n");
12846     } else {
12847         /* Currently GNU Chess doesn't offer or accept Adjourns */
12848     }
12849 }
12850
12851
12852 void
12853 AbortEvent()
12854 {
12855     /* Offer Abort or accept pending Abort offer from opponent */
12856
12857     if (appData.icsActive) {
12858         SendToICS(ics_prefix);
12859         SendToICS("abort\n");
12860     } else {
12861         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12862     }
12863 }
12864
12865 void
12866 ResignEvent()
12867 {
12868     /* Resign.  You can do this even if it's not your turn. */
12869
12870     if (appData.icsActive) {
12871         SendToICS(ics_prefix);
12872         SendToICS("resign\n");
12873     } else {
12874         switch (gameMode) {
12875           case MachinePlaysWhite:
12876             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12877             break;
12878           case MachinePlaysBlack:
12879             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12880             break;
12881           case EditGame:
12882             if (cmailMsgLoaded) {
12883                 TruncateGame();
12884                 if (WhiteOnMove(cmailOldMove)) {
12885                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12886                 } else {
12887                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12888                 }
12889                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12890             }
12891             break;
12892           default:
12893             break;
12894         }
12895     }
12896 }
12897
12898
12899 void
12900 StopObservingEvent()
12901 {
12902     /* Stop observing current games */
12903     SendToICS(ics_prefix);
12904     SendToICS("unobserve\n");
12905 }
12906
12907 void
12908 StopExaminingEvent()
12909 {
12910     /* Stop observing current game */
12911     SendToICS(ics_prefix);
12912     SendToICS("unexamine\n");
12913 }
12914
12915 void
12916 ForwardInner(target)
12917      int target;
12918 {
12919     int limit;
12920
12921     if (appData.debugMode)
12922         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12923                 target, currentMove, forwardMostMove);
12924
12925     if (gameMode == EditPosition)
12926       return;
12927
12928     if (gameMode == PlayFromGameFile && !pausing)
12929       PauseEvent();
12930
12931     if (gameMode == IcsExamining && pausing)
12932       limit = pauseExamForwardMostMove;
12933     else
12934       limit = forwardMostMove;
12935
12936     if (target > limit) target = limit;
12937
12938     if (target > 0 && moveList[target - 1][0]) {
12939         int fromX, fromY, toX, toY;
12940         toX = moveList[target - 1][2] - AAA;
12941         toY = moveList[target - 1][3] - ONE;
12942         if (moveList[target - 1][1] == '@') {
12943             if (appData.highlightLastMove) {
12944                 SetHighlights(-1, -1, toX, toY);
12945             }
12946         } else {
12947             fromX = moveList[target - 1][0] - AAA;
12948             fromY = moveList[target - 1][1] - ONE;
12949             if (target == currentMove + 1) {
12950                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12951             }
12952             if (appData.highlightLastMove) {
12953                 SetHighlights(fromX, fromY, toX, toY);
12954             }
12955         }
12956     }
12957     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12958         gameMode == Training || gameMode == PlayFromGameFile ||
12959         gameMode == AnalyzeFile) {
12960         while (currentMove < target) {
12961             SendMoveToProgram(currentMove++, &first);
12962         }
12963     } else {
12964         currentMove = target;
12965     }
12966
12967     if (gameMode == EditGame || gameMode == EndOfGame) {
12968         whiteTimeRemaining = timeRemaining[0][currentMove];
12969         blackTimeRemaining = timeRemaining[1][currentMove];
12970     }
12971     DisplayBothClocks();
12972     DisplayMove(currentMove - 1);
12973     DrawPosition(FALSE, boards[currentMove]);
12974     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12975     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12976         DisplayComment(currentMove - 1, commentList[currentMove]);
12977     }
12978 }
12979
12980
12981 void
12982 ForwardEvent()
12983 {
12984     if (gameMode == IcsExamining && !pausing) {
12985         SendToICS(ics_prefix);
12986         SendToICS("forward\n");
12987     } else {
12988         ForwardInner(currentMove + 1);
12989     }
12990 }
12991
12992 void
12993 ToEndEvent()
12994 {
12995     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12996         /* to optimze, we temporarily turn off analysis mode while we feed
12997          * the remaining moves to the engine. Otherwise we get analysis output
12998          * after each move.
12999          */
13000         if (first.analysisSupport) {
13001           SendToProgram("exit\nforce\n", &first);
13002           first.analyzing = FALSE;
13003         }
13004     }
13005
13006     if (gameMode == IcsExamining && !pausing) {
13007         SendToICS(ics_prefix);
13008         SendToICS("forward 999999\n");
13009     } else {
13010         ForwardInner(forwardMostMove);
13011     }
13012
13013     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13014         /* we have fed all the moves, so reactivate analysis mode */
13015         SendToProgram("analyze\n", &first);
13016         first.analyzing = TRUE;
13017         /*first.maybeThinking = TRUE;*/
13018         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13019     }
13020 }
13021
13022 void
13023 BackwardInner(target)
13024      int target;
13025 {
13026     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13027
13028     if (appData.debugMode)
13029         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13030                 target, currentMove, forwardMostMove);
13031
13032     if (gameMode == EditPosition) return;
13033     if (currentMove <= backwardMostMove) {
13034         ClearHighlights();
13035         DrawPosition(full_redraw, boards[currentMove]);
13036         return;
13037     }
13038     if (gameMode == PlayFromGameFile && !pausing)
13039       PauseEvent();
13040
13041     if (moveList[target][0]) {
13042         int fromX, fromY, toX, toY;
13043         toX = moveList[target][2] - AAA;
13044         toY = moveList[target][3] - ONE;
13045         if (moveList[target][1] == '@') {
13046             if (appData.highlightLastMove) {
13047                 SetHighlights(-1, -1, toX, toY);
13048             }
13049         } else {
13050             fromX = moveList[target][0] - AAA;
13051             fromY = moveList[target][1] - ONE;
13052             if (target == currentMove - 1) {
13053                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13054             }
13055             if (appData.highlightLastMove) {
13056                 SetHighlights(fromX, fromY, toX, toY);
13057             }
13058         }
13059     }
13060     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13061         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13062         while (currentMove > target) {
13063             SendToProgram("undo\n", &first);
13064             currentMove--;
13065         }
13066     } else {
13067         currentMove = target;
13068     }
13069
13070     if (gameMode == EditGame || gameMode == EndOfGame) {
13071         whiteTimeRemaining = timeRemaining[0][currentMove];
13072         blackTimeRemaining = timeRemaining[1][currentMove];
13073     }
13074     DisplayBothClocks();
13075     DisplayMove(currentMove - 1);
13076     DrawPosition(full_redraw, boards[currentMove]);
13077     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13078     // [HGM] PV info: routine tests if comment empty
13079     DisplayComment(currentMove - 1, commentList[currentMove]);
13080 }
13081
13082 void
13083 BackwardEvent()
13084 {
13085     if (gameMode == IcsExamining && !pausing) {
13086         SendToICS(ics_prefix);
13087         SendToICS("backward\n");
13088     } else {
13089         BackwardInner(currentMove - 1);
13090     }
13091 }
13092
13093 void
13094 ToStartEvent()
13095 {
13096     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13097         /* to optimize, we temporarily turn off analysis mode while we undo
13098          * all the moves. Otherwise we get analysis output after each undo.
13099          */
13100         if (first.analysisSupport) {
13101           SendToProgram("exit\nforce\n", &first);
13102           first.analyzing = FALSE;
13103         }
13104     }
13105
13106     if (gameMode == IcsExamining && !pausing) {
13107         SendToICS(ics_prefix);
13108         SendToICS("backward 999999\n");
13109     } else {
13110         BackwardInner(backwardMostMove);
13111     }
13112
13113     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13114         /* we have fed all the moves, so reactivate analysis mode */
13115         SendToProgram("analyze\n", &first);
13116         first.analyzing = TRUE;
13117         /*first.maybeThinking = TRUE;*/
13118         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13119     }
13120 }
13121
13122 void
13123 ToNrEvent(int to)
13124 {
13125   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13126   if (to >= forwardMostMove) to = forwardMostMove;
13127   if (to <= backwardMostMove) to = backwardMostMove;
13128   if (to < currentMove) {
13129     BackwardInner(to);
13130   } else {
13131     ForwardInner(to);
13132   }
13133 }
13134
13135 void
13136 RevertEvent(Boolean annotate)
13137 {
13138     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13139         return;
13140     }
13141     if (gameMode != IcsExamining) {
13142         DisplayError(_("You are not examining a game"), 0);
13143         return;
13144     }
13145     if (pausing) {
13146         DisplayError(_("You can't revert while pausing"), 0);
13147         return;
13148     }
13149     SendToICS(ics_prefix);
13150     SendToICS("revert\n");
13151 }
13152
13153 void
13154 RetractMoveEvent()
13155 {
13156     switch (gameMode) {
13157       case MachinePlaysWhite:
13158       case MachinePlaysBlack:
13159         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13160             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13161             return;
13162         }
13163         if (forwardMostMove < 2) return;
13164         currentMove = forwardMostMove = forwardMostMove - 2;
13165         whiteTimeRemaining = timeRemaining[0][currentMove];
13166         blackTimeRemaining = timeRemaining[1][currentMove];
13167         DisplayBothClocks();
13168         DisplayMove(currentMove - 1);
13169         ClearHighlights();/*!! could figure this out*/
13170         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13171         SendToProgram("remove\n", &first);
13172         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13173         break;
13174
13175       case BeginningOfGame:
13176       default:
13177         break;
13178
13179       case IcsPlayingWhite:
13180       case IcsPlayingBlack:
13181         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13182             SendToICS(ics_prefix);
13183             SendToICS("takeback 2\n");
13184         } else {
13185             SendToICS(ics_prefix);
13186             SendToICS("takeback 1\n");
13187         }
13188         break;
13189     }
13190 }
13191
13192 void
13193 MoveNowEvent()
13194 {
13195     ChessProgramState *cps;
13196
13197     switch (gameMode) {
13198       case MachinePlaysWhite:
13199         if (!WhiteOnMove(forwardMostMove)) {
13200             DisplayError(_("It is your turn"), 0);
13201             return;
13202         }
13203         cps = &first;
13204         break;
13205       case MachinePlaysBlack:
13206         if (WhiteOnMove(forwardMostMove)) {
13207             DisplayError(_("It is your turn"), 0);
13208             return;
13209         }
13210         cps = &first;
13211         break;
13212       case TwoMachinesPlay:
13213         if (WhiteOnMove(forwardMostMove) ==
13214             (first.twoMachinesColor[0] == 'w')) {
13215             cps = &first;
13216         } else {
13217             cps = &second;
13218         }
13219         break;
13220       case BeginningOfGame:
13221       default:
13222         return;
13223     }
13224     SendToProgram("?\n", cps);
13225 }
13226
13227 void
13228 TruncateGameEvent()
13229 {
13230     EditGameEvent();
13231     if (gameMode != EditGame) return;
13232     TruncateGame();
13233 }
13234
13235 void
13236 TruncateGame()
13237 {
13238     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13239     if (forwardMostMove > currentMove) {
13240         if (gameInfo.resultDetails != NULL) {
13241             free(gameInfo.resultDetails);
13242             gameInfo.resultDetails = NULL;
13243             gameInfo.result = GameUnfinished;
13244         }
13245         forwardMostMove = currentMove;
13246         HistorySet(parseList, backwardMostMove, forwardMostMove,
13247                    currentMove-1);
13248     }
13249 }
13250
13251 void
13252 HintEvent()
13253 {
13254     if (appData.noChessProgram) return;
13255     switch (gameMode) {
13256       case MachinePlaysWhite:
13257         if (WhiteOnMove(forwardMostMove)) {
13258             DisplayError(_("Wait until your turn"), 0);
13259             return;
13260         }
13261         break;
13262       case BeginningOfGame:
13263       case MachinePlaysBlack:
13264         if (!WhiteOnMove(forwardMostMove)) {
13265             DisplayError(_("Wait until your turn"), 0);
13266             return;
13267         }
13268         break;
13269       default:
13270         DisplayError(_("No hint available"), 0);
13271         return;
13272     }
13273     SendToProgram("hint\n", &first);
13274     hintRequested = TRUE;
13275 }
13276
13277 void
13278 BookEvent()
13279 {
13280     if (appData.noChessProgram) return;
13281     switch (gameMode) {
13282       case MachinePlaysWhite:
13283         if (WhiteOnMove(forwardMostMove)) {
13284             DisplayError(_("Wait until your turn"), 0);
13285             return;
13286         }
13287         break;
13288       case BeginningOfGame:
13289       case MachinePlaysBlack:
13290         if (!WhiteOnMove(forwardMostMove)) {
13291             DisplayError(_("Wait until your turn"), 0);
13292             return;
13293         }
13294         break;
13295       case EditPosition:
13296         EditPositionDone(TRUE);
13297         break;
13298       case TwoMachinesPlay:
13299         return;
13300       default:
13301         break;
13302     }
13303     SendToProgram("bk\n", &first);
13304     bookOutput[0] = NULLCHAR;
13305     bookRequested = TRUE;
13306 }
13307
13308 void
13309 AboutGameEvent()
13310 {
13311     char *tags = PGNTags(&gameInfo);
13312     TagsPopUp(tags, CmailMsg());
13313     free(tags);
13314 }
13315
13316 /* end button procedures */
13317
13318 void
13319 PrintPosition(fp, move)
13320      FILE *fp;
13321      int move;
13322 {
13323     int i, j;
13324
13325     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13326         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13327             char c = PieceToChar(boards[move][i][j]);
13328             fputc(c == 'x' ? '.' : c, fp);
13329             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13330         }
13331     }
13332     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13333       fprintf(fp, "white to play\n");
13334     else
13335       fprintf(fp, "black to play\n");
13336 }
13337
13338 void
13339 PrintOpponents(fp)
13340      FILE *fp;
13341 {
13342     if (gameInfo.white != NULL) {
13343         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13344     } else {
13345         fprintf(fp, "\n");
13346     }
13347 }
13348
13349 /* Find last component of program's own name, using some heuristics */
13350 void
13351 TidyProgramName(prog, host, buf)
13352      char *prog, *host, buf[MSG_SIZ];
13353 {
13354     char *p, *q;
13355     int local = (strcmp(host, "localhost") == 0);
13356     while (!local && (p = strchr(prog, ';')) != NULL) {
13357         p++;
13358         while (*p == ' ') p++;
13359         prog = p;
13360     }
13361     if (*prog == '"' || *prog == '\'') {
13362         q = strchr(prog + 1, *prog);
13363     } else {
13364         q = strchr(prog, ' ');
13365     }
13366     if (q == NULL) q = prog + strlen(prog);
13367     p = q;
13368     while (p >= prog && *p != '/' && *p != '\\') p--;
13369     p++;
13370     if(p == prog && *p == '"') p++;
13371     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13372     memcpy(buf, p, q - p);
13373     buf[q - p] = NULLCHAR;
13374     if (!local) {
13375         strcat(buf, "@");
13376         strcat(buf, host);
13377     }
13378 }
13379
13380 char *
13381 TimeControlTagValue()
13382 {
13383     char buf[MSG_SIZ];
13384     if (!appData.clockMode) {
13385       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13386     } else if (movesPerSession > 0) {
13387       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13388     } else if (timeIncrement == 0) {
13389       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13390     } else {
13391       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13392     }
13393     return StrSave(buf);
13394 }
13395
13396 void
13397 SetGameInfo()
13398 {
13399     /* This routine is used only for certain modes */
13400     VariantClass v = gameInfo.variant;
13401     ChessMove r = GameUnfinished;
13402     char *p = NULL;
13403
13404     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13405         r = gameInfo.result;
13406         p = gameInfo.resultDetails;
13407         gameInfo.resultDetails = NULL;
13408     }
13409     ClearGameInfo(&gameInfo);
13410     gameInfo.variant = v;
13411
13412     switch (gameMode) {
13413       case MachinePlaysWhite:
13414         gameInfo.event = StrSave( appData.pgnEventHeader );
13415         gameInfo.site = StrSave(HostName());
13416         gameInfo.date = PGNDate();
13417         gameInfo.round = StrSave("-");
13418         gameInfo.white = StrSave(first.tidy);
13419         gameInfo.black = StrSave(UserName());
13420         gameInfo.timeControl = TimeControlTagValue();
13421         break;
13422
13423       case MachinePlaysBlack:
13424         gameInfo.event = StrSave( appData.pgnEventHeader );
13425         gameInfo.site = StrSave(HostName());
13426         gameInfo.date = PGNDate();
13427         gameInfo.round = StrSave("-");
13428         gameInfo.white = StrSave(UserName());
13429         gameInfo.black = StrSave(first.tidy);
13430         gameInfo.timeControl = TimeControlTagValue();
13431         break;
13432
13433       case TwoMachinesPlay:
13434         gameInfo.event = StrSave( appData.pgnEventHeader );
13435         gameInfo.site = StrSave(HostName());
13436         gameInfo.date = PGNDate();
13437         if (matchGame > 0) {
13438             char buf[MSG_SIZ];
13439             snprintf(buf, MSG_SIZ, "%d", matchGame);
13440             gameInfo.round = StrSave(buf);
13441         } else {
13442             gameInfo.round = StrSave("-");
13443         }
13444         if (first.twoMachinesColor[0] == 'w') {
13445             gameInfo.white = StrSave(first.tidy);
13446             gameInfo.black = StrSave(second.tidy);
13447         } else {
13448             gameInfo.white = StrSave(second.tidy);
13449             gameInfo.black = StrSave(first.tidy);
13450         }
13451         gameInfo.timeControl = TimeControlTagValue();
13452         break;
13453
13454       case EditGame:
13455         gameInfo.event = StrSave("Edited game");
13456         gameInfo.site = StrSave(HostName());
13457         gameInfo.date = PGNDate();
13458         gameInfo.round = StrSave("-");
13459         gameInfo.white = StrSave("-");
13460         gameInfo.black = StrSave("-");
13461         gameInfo.result = r;
13462         gameInfo.resultDetails = p;
13463         break;
13464
13465       case EditPosition:
13466         gameInfo.event = StrSave("Edited position");
13467         gameInfo.site = StrSave(HostName());
13468         gameInfo.date = PGNDate();
13469         gameInfo.round = StrSave("-");
13470         gameInfo.white = StrSave("-");
13471         gameInfo.black = StrSave("-");
13472         break;
13473
13474       case IcsPlayingWhite:
13475       case IcsPlayingBlack:
13476       case IcsObserving:
13477       case IcsExamining:
13478         break;
13479
13480       case PlayFromGameFile:
13481         gameInfo.event = StrSave("Game from non-PGN file");
13482         gameInfo.site = StrSave(HostName());
13483         gameInfo.date = PGNDate();
13484         gameInfo.round = StrSave("-");
13485         gameInfo.white = StrSave("?");
13486         gameInfo.black = StrSave("?");
13487         break;
13488
13489       default:
13490         break;
13491     }
13492 }
13493
13494 void
13495 ReplaceComment(index, text)
13496      int index;
13497      char *text;
13498 {
13499     int len;
13500     char *p;
13501     float score;
13502
13503     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13504        pvInfoList[index-1].depth == len &&
13505        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13506        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13507     while (*text == '\n') text++;
13508     len = strlen(text);
13509     while (len > 0 && text[len - 1] == '\n') len--;
13510
13511     if (commentList[index] != NULL)
13512       free(commentList[index]);
13513
13514     if (len == 0) {
13515         commentList[index] = NULL;
13516         return;
13517     }
13518   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13519       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13520       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13521     commentList[index] = (char *) malloc(len + 2);
13522     strncpy(commentList[index], text, len);
13523     commentList[index][len] = '\n';
13524     commentList[index][len + 1] = NULLCHAR;
13525   } else {
13526     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13527     char *p;
13528     commentList[index] = (char *) malloc(len + 7);
13529     safeStrCpy(commentList[index], "{\n", 3);
13530     safeStrCpy(commentList[index]+2, text, len+1);
13531     commentList[index][len+2] = NULLCHAR;
13532     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13533     strcat(commentList[index], "\n}\n");
13534   }
13535 }
13536
13537 void
13538 CrushCRs(text)
13539      char *text;
13540 {
13541   char *p = text;
13542   char *q = text;
13543   char ch;
13544
13545   do {
13546     ch = *p++;
13547     if (ch == '\r') continue;
13548     *q++ = ch;
13549   } while (ch != '\0');
13550 }
13551
13552 void
13553 AppendComment(index, text, addBraces)
13554      int index;
13555      char *text;
13556      Boolean addBraces; // [HGM] braces: tells if we should add {}
13557 {
13558     int oldlen, len;
13559     char *old;
13560
13561 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13562     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13563
13564     CrushCRs(text);
13565     while (*text == '\n') text++;
13566     len = strlen(text);
13567     while (len > 0 && text[len - 1] == '\n') len--;
13568
13569     if (len == 0) return;
13570
13571     if (commentList[index] != NULL) {
13572         old = commentList[index];
13573         oldlen = strlen(old);
13574         while(commentList[index][oldlen-1] ==  '\n')
13575           commentList[index][--oldlen] = NULLCHAR;
13576         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13577         safeStrCpy(commentList[index], old, oldlen + len + 6);
13578         free(old);
13579         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13580         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13581           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13582           while (*text == '\n') { text++; len--; }
13583           commentList[index][--oldlen] = NULLCHAR;
13584       }
13585         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13586         else          strcat(commentList[index], "\n");
13587         strcat(commentList[index], text);
13588         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13589         else          strcat(commentList[index], "\n");
13590     } else {
13591         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13592         if(addBraces)
13593           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13594         else commentList[index][0] = NULLCHAR;
13595         strcat(commentList[index], text);
13596         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13597         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13598     }
13599 }
13600
13601 static char * FindStr( char * text, char * sub_text )
13602 {
13603     char * result = strstr( text, sub_text );
13604
13605     if( result != NULL ) {
13606         result += strlen( sub_text );
13607     }
13608
13609     return result;
13610 }
13611
13612 /* [AS] Try to extract PV info from PGN comment */
13613 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13614 char *GetInfoFromComment( int index, char * text )
13615 {
13616     char * sep = text, *p;
13617
13618     if( text != NULL && index > 0 ) {
13619         int score = 0;
13620         int depth = 0;
13621         int time = -1, sec = 0, deci;
13622         char * s_eval = FindStr( text, "[%eval " );
13623         char * s_emt = FindStr( text, "[%emt " );
13624
13625         if( s_eval != NULL || s_emt != NULL ) {
13626             /* New style */
13627             char delim;
13628
13629             if( s_eval != NULL ) {
13630                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13631                     return text;
13632                 }
13633
13634                 if( delim != ']' ) {
13635                     return text;
13636                 }
13637             }
13638
13639             if( s_emt != NULL ) {
13640             }
13641                 return text;
13642         }
13643         else {
13644             /* We expect something like: [+|-]nnn.nn/dd */
13645             int score_lo = 0;
13646
13647             if(*text != '{') return text; // [HGM] braces: must be normal comment
13648
13649             sep = strchr( text, '/' );
13650             if( sep == NULL || sep < (text+4) ) {
13651                 return text;
13652             }
13653
13654             p = text;
13655             if(p[1] == '(') { // comment starts with PV
13656                p = strchr(p, ')'); // locate end of PV
13657                if(p == NULL || sep < p+5) return text;
13658                // at this point we have something like "{(.*) +0.23/6 ..."
13659                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13660                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13661                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13662             }
13663             time = -1; sec = -1; deci = -1;
13664             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13665                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13666                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13667                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13668                 return text;
13669             }
13670
13671             if( score_lo < 0 || score_lo >= 100 ) {
13672                 return text;
13673             }
13674
13675             if(sec >= 0) time = 600*time + 10*sec; else
13676             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13677
13678             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13679
13680             /* [HGM] PV time: now locate end of PV info */
13681             while( *++sep >= '0' && *sep <= '9'); // strip depth
13682             if(time >= 0)
13683             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13684             if(sec >= 0)
13685             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13686             if(deci >= 0)
13687             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13688             while(*sep == ' ') sep++;
13689         }
13690
13691         if( depth <= 0 ) {
13692             return text;
13693         }
13694
13695         if( time < 0 ) {
13696             time = -1;
13697         }
13698
13699         pvInfoList[index-1].depth = depth;
13700         pvInfoList[index-1].score = score;
13701         pvInfoList[index-1].time  = 10*time; // centi-sec
13702         if(*sep == '}') *sep = 0; else *--sep = '{';
13703         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13704     }
13705     return sep;
13706 }
13707
13708 void
13709 SendToProgram(message, cps)
13710      char *message;
13711      ChessProgramState *cps;
13712 {
13713     int count, outCount, error;
13714     char buf[MSG_SIZ];
13715
13716     if (cps->pr == NULL) return;
13717     Attention(cps);
13718
13719     if (appData.debugMode) {
13720         TimeMark now;
13721         GetTimeMark(&now);
13722         fprintf(debugFP, "%ld >%-6s: %s",
13723                 SubtractTimeMarks(&now, &programStartTime),
13724                 cps->which, message);
13725     }
13726
13727     count = strlen(message);
13728     outCount = OutputToProcess(cps->pr, message, count, &error);
13729     if (outCount < count && !exiting
13730                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13731       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13732         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13733             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13734                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13735                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13736             } else {
13737                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13738             }
13739             gameInfo.resultDetails = StrSave(buf);
13740         }
13741         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13742     }
13743 }
13744
13745 void
13746 ReceiveFromProgram(isr, closure, message, count, error)
13747      InputSourceRef isr;
13748      VOIDSTAR closure;
13749      char *message;
13750      int count;
13751      int error;
13752 {
13753     char *end_str;
13754     char buf[MSG_SIZ];
13755     ChessProgramState *cps = (ChessProgramState *)closure;
13756
13757     if (isr != cps->isr) return; /* Killed intentionally */
13758     if (count <= 0) {
13759         if (count == 0) {
13760             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13761                     _(cps->which), cps->program);
13762         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13763                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13764                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13765                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13766                 } else {
13767                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13768                 }
13769                 gameInfo.resultDetails = StrSave(buf);
13770             }
13771             RemoveInputSource(cps->isr);
13772             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13773         } else {
13774             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13775                     _(cps->which), cps->program);
13776             RemoveInputSource(cps->isr);
13777
13778             /* [AS] Program is misbehaving badly... kill it */
13779             if( count == -2 ) {
13780                 DestroyChildProcess( cps->pr, 9 );
13781                 cps->pr = NoProc;
13782             }
13783
13784             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13785         }
13786         return;
13787     }
13788
13789     if ((end_str = strchr(message, '\r')) != NULL)
13790       *end_str = NULLCHAR;
13791     if ((end_str = strchr(message, '\n')) != NULL)
13792       *end_str = NULLCHAR;
13793
13794     if (appData.debugMode) {
13795         TimeMark now; int print = 1;
13796         char *quote = ""; char c; int i;
13797
13798         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13799                 char start = message[0];
13800                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13801                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13802                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13803                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13804                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13805                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13806                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13807                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13808                    sscanf(message, "hint: %c", &c)!=1 && 
13809                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13810                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13811                     print = (appData.engineComments >= 2);
13812                 }
13813                 message[0] = start; // restore original message
13814         }
13815         if(print) {
13816                 GetTimeMark(&now);
13817                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13818                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13819                         quote,
13820                         message);
13821         }
13822     }
13823
13824     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13825     if (appData.icsEngineAnalyze) {
13826         if (strstr(message, "whisper") != NULL ||
13827              strstr(message, "kibitz") != NULL ||
13828             strstr(message, "tellics") != NULL) return;
13829     }
13830
13831     HandleMachineMove(message, cps);
13832 }
13833
13834
13835 void
13836 SendTimeControl(cps, mps, tc, inc, sd, st)
13837      ChessProgramState *cps;
13838      int mps, inc, sd, st;
13839      long tc;
13840 {
13841     char buf[MSG_SIZ];
13842     int seconds;
13843
13844     if( timeControl_2 > 0 ) {
13845         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13846             tc = timeControl_2;
13847         }
13848     }
13849     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13850     inc /= cps->timeOdds;
13851     st  /= cps->timeOdds;
13852
13853     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13854
13855     if (st > 0) {
13856       /* Set exact time per move, normally using st command */
13857       if (cps->stKludge) {
13858         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13859         seconds = st % 60;
13860         if (seconds == 0) {
13861           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13862         } else {
13863           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13864         }
13865       } else {
13866         snprintf(buf, MSG_SIZ, "st %d\n", st);
13867       }
13868     } else {
13869       /* Set conventional or incremental time control, using level command */
13870       if (seconds == 0) {
13871         /* Note old gnuchess bug -- minutes:seconds used to not work.
13872            Fixed in later versions, but still avoid :seconds
13873            when seconds is 0. */
13874         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13875       } else {
13876         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13877                  seconds, inc/1000.);
13878       }
13879     }
13880     SendToProgram(buf, cps);
13881
13882     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13883     /* Orthogonally, limit search to given depth */
13884     if (sd > 0) {
13885       if (cps->sdKludge) {
13886         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13887       } else {
13888         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13889       }
13890       SendToProgram(buf, cps);
13891     }
13892
13893     if(cps->nps >= 0) { /* [HGM] nps */
13894         if(cps->supportsNPS == FALSE)
13895           cps->nps = -1; // don't use if engine explicitly says not supported!
13896         else {
13897           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13898           SendToProgram(buf, cps);
13899         }
13900     }
13901 }
13902
13903 ChessProgramState *WhitePlayer()
13904 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13905 {
13906     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13907        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13908         return &second;
13909     return &first;
13910 }
13911
13912 void
13913 SendTimeRemaining(cps, machineWhite)
13914      ChessProgramState *cps;
13915      int /*boolean*/ machineWhite;
13916 {
13917     char message[MSG_SIZ];
13918     long time, otime;
13919
13920     /* Note: this routine must be called when the clocks are stopped
13921        or when they have *just* been set or switched; otherwise
13922        it will be off by the time since the current tick started.
13923     */
13924     if (machineWhite) {
13925         time = whiteTimeRemaining / 10;
13926         otime = blackTimeRemaining / 10;
13927     } else {
13928         time = blackTimeRemaining / 10;
13929         otime = whiteTimeRemaining / 10;
13930     }
13931     /* [HGM] translate opponent's time by time-odds factor */
13932     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13933     if (appData.debugMode) {
13934         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13935     }
13936
13937     if (time <= 0) time = 1;
13938     if (otime <= 0) otime = 1;
13939
13940     snprintf(message, MSG_SIZ, "time %ld\n", time);
13941     SendToProgram(message, cps);
13942
13943     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13944     SendToProgram(message, cps);
13945 }
13946
13947 int
13948 BoolFeature(p, name, loc, cps)
13949      char **p;
13950      char *name;
13951      int *loc;
13952      ChessProgramState *cps;
13953 {
13954   char buf[MSG_SIZ];
13955   int len = strlen(name);
13956   int val;
13957
13958   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13959     (*p) += len + 1;
13960     sscanf(*p, "%d", &val);
13961     *loc = (val != 0);
13962     while (**p && **p != ' ')
13963       (*p)++;
13964     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13965     SendToProgram(buf, cps);
13966     return TRUE;
13967   }
13968   return FALSE;
13969 }
13970
13971 int
13972 IntFeature(p, name, loc, cps)
13973      char **p;
13974      char *name;
13975      int *loc;
13976      ChessProgramState *cps;
13977 {
13978   char buf[MSG_SIZ];
13979   int len = strlen(name);
13980   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13981     (*p) += len + 1;
13982     sscanf(*p, "%d", loc);
13983     while (**p && **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 StringFeature(p, name, loc, cps)
13993      char **p;
13994      char *name;
13995      char loc[];
13996      ChessProgramState *cps;
13997 {
13998   char buf[MSG_SIZ];
13999   int len = strlen(name);
14000   if (strncmp((*p), name, len) == 0
14001       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14002     (*p) += len + 2;
14003     sscanf(*p, "%[^\"]", loc);
14004     while (**p && **p != '\"') (*p)++;
14005     if (**p == '\"') (*p)++;
14006     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14007     SendToProgram(buf, cps);
14008     return TRUE;
14009   }
14010   return FALSE;
14011 }
14012
14013 int
14014 ParseOption(Option *opt, ChessProgramState *cps)
14015 // [HGM] options: process the string that defines an engine option, and determine
14016 // name, type, default value, and allowed value range
14017 {
14018         char *p, *q, buf[MSG_SIZ];
14019         int n, min = (-1)<<31, max = 1<<31, def;
14020
14021         if(p = strstr(opt->name, " -spin ")) {
14022             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14023             if(max < min) max = min; // enforce consistency
14024             if(def < min) def = min;
14025             if(def > max) def = max;
14026             opt->value = def;
14027             opt->min = min;
14028             opt->max = max;
14029             opt->type = Spin;
14030         } else if((p = strstr(opt->name, " -slider "))) {
14031             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14032             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14033             if(max < min) max = min; // enforce consistency
14034             if(def < min) def = min;
14035             if(def > max) def = max;
14036             opt->value = def;
14037             opt->min = min;
14038             opt->max = max;
14039             opt->type = Spin; // Slider;
14040         } else if((p = strstr(opt->name, " -string "))) {
14041             opt->textValue = p+9;
14042             opt->type = TextBox;
14043         } else if((p = strstr(opt->name, " -file "))) {
14044             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14045             opt->textValue = p+7;
14046             opt->type = FileName; // FileName;
14047         } else if((p = strstr(opt->name, " -path "))) {
14048             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14049             opt->textValue = p+7;
14050             opt->type = PathName; // PathName;
14051         } else if(p = strstr(opt->name, " -check ")) {
14052             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14053             opt->value = (def != 0);
14054             opt->type = CheckBox;
14055         } else if(p = strstr(opt->name, " -combo ")) {
14056             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14057             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14058             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14059             opt->value = n = 0;
14060             while(q = StrStr(q, " /// ")) {
14061                 n++; *q = 0;    // count choices, and null-terminate each of them
14062                 q += 5;
14063                 if(*q == '*') { // remember default, which is marked with * prefix
14064                     q++;
14065                     opt->value = n;
14066                 }
14067                 cps->comboList[cps->comboCnt++] = q;
14068             }
14069             cps->comboList[cps->comboCnt++] = NULL;
14070             opt->max = n + 1;
14071             opt->type = ComboBox;
14072         } else if(p = strstr(opt->name, " -button")) {
14073             opt->type = Button;
14074         } else if(p = strstr(opt->name, " -save")) {
14075             opt->type = SaveButton;
14076         } else return FALSE;
14077         *p = 0; // terminate option name
14078         // now look if the command-line options define a setting for this engine option.
14079         if(cps->optionSettings && cps->optionSettings[0])
14080             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14081         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14082           snprintf(buf, MSG_SIZ, "option %s", p);
14083                 if(p = strstr(buf, ",")) *p = 0;
14084                 if(q = strchr(buf, '=')) switch(opt->type) {
14085                     case ComboBox:
14086                         for(n=0; n<opt->max; n++)
14087                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14088                         break;
14089                     case TextBox:
14090                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14091                         break;
14092                     case Spin:
14093                     case CheckBox:
14094                         opt->value = atoi(q+1);
14095                     default:
14096                         break;
14097                 }
14098                 strcat(buf, "\n");
14099                 SendToProgram(buf, cps);
14100         }
14101         return TRUE;
14102 }
14103
14104 void
14105 FeatureDone(cps, val)
14106      ChessProgramState* cps;
14107      int val;
14108 {
14109   DelayedEventCallback cb = GetDelayedEvent();
14110   if ((cb == InitBackEnd3 && cps == &first) ||
14111       (cb == SettingsMenuIfReady && cps == &second) ||
14112       (cb == TwoMachinesEventIfReady && cps == &second)) {
14113     CancelDelayedEvent();
14114     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14115   }
14116   cps->initDone = val;
14117 }
14118
14119 /* Parse feature command from engine */
14120 void
14121 ParseFeatures(args, cps)
14122      char* args;
14123      ChessProgramState *cps;
14124 {
14125   char *p = args;
14126   char *q;
14127   int val;
14128   char buf[MSG_SIZ];
14129
14130   for (;;) {
14131     while (*p == ' ') p++;
14132     if (*p == NULLCHAR) return;
14133
14134     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14135     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14136     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14137     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14138     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14139     if (BoolFeature(&p, "reuse", &val, cps)) {
14140       /* Engine can disable reuse, but can't enable it if user said no */
14141       if (!val) cps->reuse = FALSE;
14142       continue;
14143     }
14144     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14145     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14146       if (gameMode == TwoMachinesPlay) {
14147         DisplayTwoMachinesTitle();
14148       } else {
14149         DisplayTitle("");
14150       }
14151       continue;
14152     }
14153     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14154     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14155     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14156     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14157     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14158     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14159     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14160     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14161     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14162     if (IntFeature(&p, "done", &val, cps)) {
14163       FeatureDone(cps, val);
14164       continue;
14165     }
14166     /* Added by Tord: */
14167     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14168     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14169     /* End of additions by Tord */
14170
14171     /* [HGM] added features: */
14172     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14173     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14174     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14175     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14176     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14177     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14178     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14179         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14180           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14181             SendToProgram(buf, cps);
14182             continue;
14183         }
14184         if(cps->nrOptions >= MAX_OPTIONS) {
14185             cps->nrOptions--;
14186             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14187             DisplayError(buf, 0);
14188         }
14189         continue;
14190     }
14191     /* End of additions by HGM */
14192
14193     /* unknown feature: complain and skip */
14194     q = p;
14195     while (*q && *q != '=') q++;
14196     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14197     SendToProgram(buf, cps);
14198     p = q;
14199     if (*p == '=') {
14200       p++;
14201       if (*p == '\"') {
14202         p++;
14203         while (*p && *p != '\"') p++;
14204         if (*p == '\"') p++;
14205       } else {
14206         while (*p && *p != ' ') p++;
14207       }
14208     }
14209   }
14210
14211 }
14212
14213 void
14214 PeriodicUpdatesEvent(newState)
14215      int newState;
14216 {
14217     if (newState == appData.periodicUpdates)
14218       return;
14219
14220     appData.periodicUpdates=newState;
14221
14222     /* Display type changes, so update it now */
14223 //    DisplayAnalysis();
14224
14225     /* Get the ball rolling again... */
14226     if (newState) {
14227         AnalysisPeriodicEvent(1);
14228         StartAnalysisClock();
14229     }
14230 }
14231
14232 void
14233 PonderNextMoveEvent(newState)
14234      int newState;
14235 {
14236     if (newState == appData.ponderNextMove) return;
14237     if (gameMode == EditPosition) EditPositionDone(TRUE);
14238     if (newState) {
14239         SendToProgram("hard\n", &first);
14240         if (gameMode == TwoMachinesPlay) {
14241             SendToProgram("hard\n", &second);
14242         }
14243     } else {
14244         SendToProgram("easy\n", &first);
14245         thinkOutput[0] = NULLCHAR;
14246         if (gameMode == TwoMachinesPlay) {
14247             SendToProgram("easy\n", &second);
14248         }
14249     }
14250     appData.ponderNextMove = newState;
14251 }
14252
14253 void
14254 NewSettingEvent(option, feature, command, value)
14255      char *command;
14256      int option, value, *feature;
14257 {
14258     char buf[MSG_SIZ];
14259
14260     if (gameMode == EditPosition) EditPositionDone(TRUE);
14261     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14262     if(feature == NULL || *feature) SendToProgram(buf, &first);
14263     if (gameMode == TwoMachinesPlay) {
14264         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14265     }
14266 }
14267
14268 void
14269 ShowThinkingEvent()
14270 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14271 {
14272     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14273     int newState = appData.showThinking
14274         // [HGM] thinking: other features now need thinking output as well
14275         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14276
14277     if (oldState == newState) return;
14278     oldState = newState;
14279     if (gameMode == EditPosition) EditPositionDone(TRUE);
14280     if (oldState) {
14281         SendToProgram("post\n", &first);
14282         if (gameMode == TwoMachinesPlay) {
14283             SendToProgram("post\n", &second);
14284         }
14285     } else {
14286         SendToProgram("nopost\n", &first);
14287         thinkOutput[0] = NULLCHAR;
14288         if (gameMode == TwoMachinesPlay) {
14289             SendToProgram("nopost\n", &second);
14290         }
14291     }
14292 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14293 }
14294
14295 void
14296 AskQuestionEvent(title, question, replyPrefix, which)
14297      char *title; char *question; char *replyPrefix; char *which;
14298 {
14299   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14300   if (pr == NoProc) return;
14301   AskQuestion(title, question, replyPrefix, pr);
14302 }
14303
14304 void
14305 DisplayMove(moveNumber)
14306      int moveNumber;
14307 {
14308     char message[MSG_SIZ];
14309     char res[MSG_SIZ];
14310     char cpThinkOutput[MSG_SIZ];
14311
14312     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14313
14314     if (moveNumber == forwardMostMove - 1 ||
14315         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14316
14317         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14318
14319         if (strchr(cpThinkOutput, '\n')) {
14320             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14321         }
14322     } else {
14323         *cpThinkOutput = NULLCHAR;
14324     }
14325
14326     /* [AS] Hide thinking from human user */
14327     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14328         *cpThinkOutput = NULLCHAR;
14329         if( thinkOutput[0] != NULLCHAR ) {
14330             int i;
14331
14332             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14333                 cpThinkOutput[i] = '.';
14334             }
14335             cpThinkOutput[i] = NULLCHAR;
14336             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14337         }
14338     }
14339
14340     if (moveNumber == forwardMostMove - 1 &&
14341         gameInfo.resultDetails != NULL) {
14342         if (gameInfo.resultDetails[0] == NULLCHAR) {
14343           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14344         } else {
14345           snprintf(res, MSG_SIZ, " {%s} %s",
14346                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14347         }
14348     } else {
14349         res[0] = NULLCHAR;
14350     }
14351
14352     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14353         DisplayMessage(res, cpThinkOutput);
14354     } else {
14355       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14356                 WhiteOnMove(moveNumber) ? " " : ".. ",
14357                 parseList[moveNumber], res);
14358         DisplayMessage(message, cpThinkOutput);
14359     }
14360 }
14361
14362 void
14363 DisplayComment(moveNumber, text)
14364      int moveNumber;
14365      char *text;
14366 {
14367     char title[MSG_SIZ];
14368     char buf[8000]; // comment can be long!
14369     int score, depth;
14370
14371     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14372       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14373     } else {
14374       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14375               WhiteOnMove(moveNumber) ? " " : ".. ",
14376               parseList[moveNumber]);
14377     }
14378     // [HGM] PV info: display PV info together with (or as) comment
14379     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14380       if(text == NULL) text = "";
14381       score = pvInfoList[moveNumber].score;
14382       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14383               depth, (pvInfoList[moveNumber].time+50)/100, text);
14384       text = buf;
14385     }
14386     if (text != NULL && (appData.autoDisplayComment || commentUp))
14387         CommentPopUp(title, text);
14388 }
14389
14390 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14391  * might be busy thinking or pondering.  It can be omitted if your
14392  * gnuchess is configured to stop thinking immediately on any user
14393  * input.  However, that gnuchess feature depends on the FIONREAD
14394  * ioctl, which does not work properly on some flavors of Unix.
14395  */
14396 void
14397 Attention(cps)
14398      ChessProgramState *cps;
14399 {
14400 #if ATTENTION
14401     if (!cps->useSigint) return;
14402     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14403     switch (gameMode) {
14404       case MachinePlaysWhite:
14405       case MachinePlaysBlack:
14406       case TwoMachinesPlay:
14407       case IcsPlayingWhite:
14408       case IcsPlayingBlack:
14409       case AnalyzeMode:
14410       case AnalyzeFile:
14411         /* Skip if we know it isn't thinking */
14412         if (!cps->maybeThinking) return;
14413         if (appData.debugMode)
14414           fprintf(debugFP, "Interrupting %s\n", cps->which);
14415         InterruptChildProcess(cps->pr);
14416         cps->maybeThinking = FALSE;
14417         break;
14418       default:
14419         break;
14420     }
14421 #endif /*ATTENTION*/
14422 }
14423
14424 int
14425 CheckFlags()
14426 {
14427     if (whiteTimeRemaining <= 0) {
14428         if (!whiteFlag) {
14429             whiteFlag = TRUE;
14430             if (appData.icsActive) {
14431                 if (appData.autoCallFlag &&
14432                     gameMode == IcsPlayingBlack && !blackFlag) {
14433                   SendToICS(ics_prefix);
14434                   SendToICS("flag\n");
14435                 }
14436             } else {
14437                 if (blackFlag) {
14438                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14439                 } else {
14440                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14441                     if (appData.autoCallFlag) {
14442                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14443                         return TRUE;
14444                     }
14445                 }
14446             }
14447         }
14448     }
14449     if (blackTimeRemaining <= 0) {
14450         if (!blackFlag) {
14451             blackFlag = TRUE;
14452             if (appData.icsActive) {
14453                 if (appData.autoCallFlag &&
14454                     gameMode == IcsPlayingWhite && !whiteFlag) {
14455                   SendToICS(ics_prefix);
14456                   SendToICS("flag\n");
14457                 }
14458             } else {
14459                 if (whiteFlag) {
14460                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14461                 } else {
14462                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14463                     if (appData.autoCallFlag) {
14464                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14465                         return TRUE;
14466                     }
14467                 }
14468             }
14469         }
14470     }
14471     return FALSE;
14472 }
14473
14474 void
14475 CheckTimeControl()
14476 {
14477     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14478         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14479
14480     /*
14481      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14482      */
14483     if ( !WhiteOnMove(forwardMostMove) ) {
14484         /* White made time control */
14485         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14486         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14487         /* [HGM] time odds: correct new time quota for time odds! */
14488                                             / WhitePlayer()->timeOdds;
14489         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14490     } else {
14491         lastBlack -= blackTimeRemaining;
14492         /* Black made time control */
14493         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14494                                             / WhitePlayer()->other->timeOdds;
14495         lastWhite = whiteTimeRemaining;
14496     }
14497 }
14498
14499 void
14500 DisplayBothClocks()
14501 {
14502     int wom = gameMode == EditPosition ?
14503       !blackPlaysFirst : WhiteOnMove(currentMove);
14504     DisplayWhiteClock(whiteTimeRemaining, wom);
14505     DisplayBlackClock(blackTimeRemaining, !wom);
14506 }
14507
14508
14509 /* Timekeeping seems to be a portability nightmare.  I think everyone
14510    has ftime(), but I'm really not sure, so I'm including some ifdefs
14511    to use other calls if you don't.  Clocks will be less accurate if
14512    you have neither ftime nor gettimeofday.
14513 */
14514
14515 /* VS 2008 requires the #include outside of the function */
14516 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14517 #include <sys/timeb.h>
14518 #endif
14519
14520 /* Get the current time as a TimeMark */
14521 void
14522 GetTimeMark(tm)
14523      TimeMark *tm;
14524 {
14525 #if HAVE_GETTIMEOFDAY
14526
14527     struct timeval timeVal;
14528     struct timezone timeZone;
14529
14530     gettimeofday(&timeVal, &timeZone);
14531     tm->sec = (long) timeVal.tv_sec;
14532     tm->ms = (int) (timeVal.tv_usec / 1000L);
14533
14534 #else /*!HAVE_GETTIMEOFDAY*/
14535 #if HAVE_FTIME
14536
14537 // include <sys/timeb.h> / moved to just above start of function
14538     struct timeb timeB;
14539
14540     ftime(&timeB);
14541     tm->sec = (long) timeB.time;
14542     tm->ms = (int) timeB.millitm;
14543
14544 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14545     tm->sec = (long) time(NULL);
14546     tm->ms = 0;
14547 #endif
14548 #endif
14549 }
14550
14551 /* Return the difference in milliseconds between two
14552    time marks.  We assume the difference will fit in a long!
14553 */
14554 long
14555 SubtractTimeMarks(tm2, tm1)
14556      TimeMark *tm2, *tm1;
14557 {
14558     return 1000L*(tm2->sec - tm1->sec) +
14559            (long) (tm2->ms - tm1->ms);
14560 }
14561
14562
14563 /*
14564  * Code to manage the game clocks.
14565  *
14566  * In tournament play, black starts the clock and then white makes a move.
14567  * We give the human user a slight advantage if he is playing white---the
14568  * clocks don't run until he makes his first move, so it takes zero time.
14569  * Also, we don't account for network lag, so we could get out of sync
14570  * with GNU Chess's clock -- but then, referees are always right.
14571  */
14572
14573 static TimeMark tickStartTM;
14574 static long intendedTickLength;
14575
14576 long
14577 NextTickLength(timeRemaining)
14578      long timeRemaining;
14579 {
14580     long nominalTickLength, nextTickLength;
14581
14582     if (timeRemaining > 0L && timeRemaining <= 10000L)
14583       nominalTickLength = 100L;
14584     else
14585       nominalTickLength = 1000L;
14586     nextTickLength = timeRemaining % nominalTickLength;
14587     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14588
14589     return nextTickLength;
14590 }
14591
14592 /* Adjust clock one minute up or down */
14593 void
14594 AdjustClock(Boolean which, int dir)
14595 {
14596     if(which) blackTimeRemaining += 60000*dir;
14597     else      whiteTimeRemaining += 60000*dir;
14598     DisplayBothClocks();
14599 }
14600
14601 /* Stop clocks and reset to a fresh time control */
14602 void
14603 ResetClocks()
14604 {
14605     (void) StopClockTimer();
14606     if (appData.icsActive) {
14607         whiteTimeRemaining = blackTimeRemaining = 0;
14608     } else if (searchTime) {
14609         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14610         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14611     } else { /* [HGM] correct new time quote for time odds */
14612         whiteTC = blackTC = fullTimeControlString;
14613         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14614         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14615     }
14616     if (whiteFlag || blackFlag) {
14617         DisplayTitle("");
14618         whiteFlag = blackFlag = FALSE;
14619     }
14620     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14621     DisplayBothClocks();
14622 }
14623
14624 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14625
14626 /* Decrement running clock by amount of time that has passed */
14627 void
14628 DecrementClocks()
14629 {
14630     long timeRemaining;
14631     long lastTickLength, fudge;
14632     TimeMark now;
14633
14634     if (!appData.clockMode) return;
14635     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14636
14637     GetTimeMark(&now);
14638
14639     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14640
14641     /* Fudge if we woke up a little too soon */
14642     fudge = intendedTickLength - lastTickLength;
14643     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14644
14645     if (WhiteOnMove(forwardMostMove)) {
14646         if(whiteNPS >= 0) lastTickLength = 0;
14647         timeRemaining = whiteTimeRemaining -= lastTickLength;
14648         if(timeRemaining < 0 && !appData.icsActive) {
14649             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14650             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14651                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14652                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14653             }
14654         }
14655         DisplayWhiteClock(whiteTimeRemaining - fudge,
14656                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14657     } else {
14658         if(blackNPS >= 0) lastTickLength = 0;
14659         timeRemaining = blackTimeRemaining -= lastTickLength;
14660         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14661             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14662             if(suddenDeath) {
14663                 blackStartMove = forwardMostMove;
14664                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14665             }
14666         }
14667         DisplayBlackClock(blackTimeRemaining - fudge,
14668                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14669     }
14670     if (CheckFlags()) return;
14671
14672     tickStartTM = now;
14673     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14674     StartClockTimer(intendedTickLength);
14675
14676     /* if the time remaining has fallen below the alarm threshold, sound the
14677      * alarm. if the alarm has sounded and (due to a takeback or time control
14678      * with increment) the time remaining has increased to a level above the
14679      * threshold, reset the alarm so it can sound again.
14680      */
14681
14682     if (appData.icsActive && appData.icsAlarm) {
14683
14684         /* make sure we are dealing with the user's clock */
14685         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14686                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14687            )) return;
14688
14689         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14690             alarmSounded = FALSE;
14691         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14692             PlayAlarmSound();
14693             alarmSounded = TRUE;
14694         }
14695     }
14696 }
14697
14698
14699 /* A player has just moved, so stop the previously running
14700    clock and (if in clock mode) start the other one.
14701    We redisplay both clocks in case we're in ICS mode, because
14702    ICS gives us an update to both clocks after every move.
14703    Note that this routine is called *after* forwardMostMove
14704    is updated, so the last fractional tick must be subtracted
14705    from the color that is *not* on move now.
14706 */
14707 void
14708 SwitchClocks(int newMoveNr)
14709 {
14710     long lastTickLength;
14711     TimeMark now;
14712     int flagged = FALSE;
14713
14714     GetTimeMark(&now);
14715
14716     if (StopClockTimer() && appData.clockMode) {
14717         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14718         if (!WhiteOnMove(forwardMostMove)) {
14719             if(blackNPS >= 0) lastTickLength = 0;
14720             blackTimeRemaining -= lastTickLength;
14721            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14722 //         if(pvInfoList[forwardMostMove].time == -1)
14723                  pvInfoList[forwardMostMove].time =               // use GUI time
14724                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14725         } else {
14726            if(whiteNPS >= 0) lastTickLength = 0;
14727            whiteTimeRemaining -= lastTickLength;
14728            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14729 //         if(pvInfoList[forwardMostMove].time == -1)
14730                  pvInfoList[forwardMostMove].time =
14731                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14732         }
14733         flagged = CheckFlags();
14734     }
14735     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14736     CheckTimeControl();
14737
14738     if (flagged || !appData.clockMode) return;
14739
14740     switch (gameMode) {
14741       case MachinePlaysBlack:
14742       case MachinePlaysWhite:
14743       case BeginningOfGame:
14744         if (pausing) return;
14745         break;
14746
14747       case EditGame:
14748       case PlayFromGameFile:
14749       case IcsExamining:
14750         return;
14751
14752       default:
14753         break;
14754     }
14755
14756     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14757         if(WhiteOnMove(forwardMostMove))
14758              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14759         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14760     }
14761
14762     tickStartTM = now;
14763     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14764       whiteTimeRemaining : blackTimeRemaining);
14765     StartClockTimer(intendedTickLength);
14766 }
14767
14768
14769 /* Stop both clocks */
14770 void
14771 StopClocks()
14772 {
14773     long lastTickLength;
14774     TimeMark now;
14775
14776     if (!StopClockTimer()) return;
14777     if (!appData.clockMode) return;
14778
14779     GetTimeMark(&now);
14780
14781     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14782     if (WhiteOnMove(forwardMostMove)) {
14783         if(whiteNPS >= 0) lastTickLength = 0;
14784         whiteTimeRemaining -= lastTickLength;
14785         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14786     } else {
14787         if(blackNPS >= 0) lastTickLength = 0;
14788         blackTimeRemaining -= lastTickLength;
14789         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14790     }
14791     CheckFlags();
14792 }
14793
14794 /* Start clock of player on move.  Time may have been reset, so
14795    if clock is already running, stop and restart it. */
14796 void
14797 StartClocks()
14798 {
14799     (void) StopClockTimer(); /* in case it was running already */
14800     DisplayBothClocks();
14801     if (CheckFlags()) return;
14802
14803     if (!appData.clockMode) return;
14804     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14805
14806     GetTimeMark(&tickStartTM);
14807     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14808       whiteTimeRemaining : blackTimeRemaining);
14809
14810    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14811     whiteNPS = blackNPS = -1;
14812     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14813        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14814         whiteNPS = first.nps;
14815     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14816        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14817         blackNPS = first.nps;
14818     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14819         whiteNPS = second.nps;
14820     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14821         blackNPS = second.nps;
14822     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14823
14824     StartClockTimer(intendedTickLength);
14825 }
14826
14827 char *
14828 TimeString(ms)
14829      long ms;
14830 {
14831     long second, minute, hour, day;
14832     char *sign = "";
14833     static char buf[32];
14834
14835     if (ms > 0 && ms <= 9900) {
14836       /* convert milliseconds to tenths, rounding up */
14837       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14838
14839       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14840       return buf;
14841     }
14842
14843     /* convert milliseconds to seconds, rounding up */
14844     /* use floating point to avoid strangeness of integer division
14845        with negative dividends on many machines */
14846     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14847
14848     if (second < 0) {
14849         sign = "-";
14850         second = -second;
14851     }
14852
14853     day = second / (60 * 60 * 24);
14854     second = second % (60 * 60 * 24);
14855     hour = second / (60 * 60);
14856     second = second % (60 * 60);
14857     minute = second / 60;
14858     second = second % 60;
14859
14860     if (day > 0)
14861       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14862               sign, day, hour, minute, second);
14863     else if (hour > 0)
14864       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14865     else
14866       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14867
14868     return buf;
14869 }
14870
14871
14872 /*
14873  * This is necessary because some C libraries aren't ANSI C compliant yet.
14874  */
14875 char *
14876 StrStr(string, match)
14877      char *string, *match;
14878 {
14879     int i, length;
14880
14881     length = strlen(match);
14882
14883     for (i = strlen(string) - length; i >= 0; i--, string++)
14884       if (!strncmp(match, string, length))
14885         return string;
14886
14887     return NULL;
14888 }
14889
14890 char *
14891 StrCaseStr(string, match)
14892      char *string, *match;
14893 {
14894     int i, j, length;
14895
14896     length = strlen(match);
14897
14898     for (i = strlen(string) - length; i >= 0; i--, string++) {
14899         for (j = 0; j < length; j++) {
14900             if (ToLower(match[j]) != ToLower(string[j]))
14901               break;
14902         }
14903         if (j == length) return string;
14904     }
14905
14906     return NULL;
14907 }
14908
14909 #ifndef _amigados
14910 int
14911 StrCaseCmp(s1, s2)
14912      char *s1, *s2;
14913 {
14914     char c1, c2;
14915
14916     for (;;) {
14917         c1 = ToLower(*s1++);
14918         c2 = ToLower(*s2++);
14919         if (c1 > c2) return 1;
14920         if (c1 < c2) return -1;
14921         if (c1 == NULLCHAR) return 0;
14922     }
14923 }
14924
14925
14926 int
14927 ToLower(c)
14928      int c;
14929 {
14930     return isupper(c) ? tolower(c) : c;
14931 }
14932
14933
14934 int
14935 ToUpper(c)
14936      int c;
14937 {
14938     return islower(c) ? toupper(c) : c;
14939 }
14940 #endif /* !_amigados    */
14941
14942 char *
14943 StrSave(s)
14944      char *s;
14945 {
14946   char *ret;
14947
14948   if ((ret = (char *) malloc(strlen(s) + 1)))
14949     {
14950       safeStrCpy(ret, s, strlen(s)+1);
14951     }
14952   return ret;
14953 }
14954
14955 char *
14956 StrSavePtr(s, savePtr)
14957      char *s, **savePtr;
14958 {
14959     if (*savePtr) {
14960         free(*savePtr);
14961     }
14962     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14963       safeStrCpy(*savePtr, s, strlen(s)+1);
14964     }
14965     return(*savePtr);
14966 }
14967
14968 char *
14969 PGNDate()
14970 {
14971     time_t clock;
14972     struct tm *tm;
14973     char buf[MSG_SIZ];
14974
14975     clock = time((time_t *)NULL);
14976     tm = localtime(&clock);
14977     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14978             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14979     return StrSave(buf);
14980 }
14981
14982
14983 char *
14984 PositionToFEN(move, overrideCastling)
14985      int move;
14986      char *overrideCastling;
14987 {
14988     int i, j, fromX, fromY, toX, toY;
14989     int whiteToPlay;
14990     char buf[128];
14991     char *p, *q;
14992     int emptycount;
14993     ChessSquare piece;
14994
14995     whiteToPlay = (gameMode == EditPosition) ?
14996       !blackPlaysFirst : (move % 2 == 0);
14997     p = buf;
14998
14999     /* Piece placement data */
15000     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15001         emptycount = 0;
15002         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15003             if (boards[move][i][j] == EmptySquare) {
15004                 emptycount++;
15005             } else { ChessSquare piece = boards[move][i][j];
15006                 if (emptycount > 0) {
15007                     if(emptycount<10) /* [HGM] can be >= 10 */
15008                         *p++ = '0' + emptycount;
15009                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15010                     emptycount = 0;
15011                 }
15012                 if(PieceToChar(piece) == '+') {
15013                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15014                     *p++ = '+';
15015                     piece = (ChessSquare)(DEMOTED piece);
15016                 }
15017                 *p++ = PieceToChar(piece);
15018                 if(p[-1] == '~') {
15019                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15020                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15021                     *p++ = '~';
15022                 }
15023             }
15024         }
15025         if (emptycount > 0) {
15026             if(emptycount<10) /* [HGM] can be >= 10 */
15027                 *p++ = '0' + emptycount;
15028             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15029             emptycount = 0;
15030         }
15031         *p++ = '/';
15032     }
15033     *(p - 1) = ' ';
15034
15035     /* [HGM] print Crazyhouse or Shogi holdings */
15036     if( gameInfo.holdingsWidth ) {
15037         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15038         q = p;
15039         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15040             piece = boards[move][i][BOARD_WIDTH-1];
15041             if( piece != EmptySquare )
15042               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15043                   *p++ = PieceToChar(piece);
15044         }
15045         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15046             piece = boards[move][BOARD_HEIGHT-i-1][0];
15047             if( piece != EmptySquare )
15048               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15049                   *p++ = PieceToChar(piece);
15050         }
15051
15052         if( q == p ) *p++ = '-';
15053         *p++ = ']';
15054         *p++ = ' ';
15055     }
15056
15057     /* Active color */
15058     *p++ = whiteToPlay ? 'w' : 'b';
15059     *p++ = ' ';
15060
15061   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15062     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15063   } else {
15064   if(nrCastlingRights) {
15065      q = p;
15066      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15067        /* [HGM] write directly from rights */
15068            if(boards[move][CASTLING][2] != NoRights &&
15069               boards[move][CASTLING][0] != NoRights   )
15070                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15071            if(boards[move][CASTLING][2] != NoRights &&
15072               boards[move][CASTLING][1] != NoRights   )
15073                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15074            if(boards[move][CASTLING][5] != NoRights &&
15075               boards[move][CASTLING][3] != NoRights   )
15076                 *p++ = boards[move][CASTLING][3] + AAA;
15077            if(boards[move][CASTLING][5] != NoRights &&
15078               boards[move][CASTLING][4] != NoRights   )
15079                 *p++ = boards[move][CASTLING][4] + AAA;
15080      } else {
15081
15082         /* [HGM] write true castling rights */
15083         if( nrCastlingRights == 6 ) {
15084             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15085                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15086             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15087                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15088             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15089                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15090             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15091                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15092         }
15093      }
15094      if (q == p) *p++ = '-'; /* No castling rights */
15095      *p++ = ' ';
15096   }
15097
15098   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15099      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15100     /* En passant target square */
15101     if (move > backwardMostMove) {
15102         fromX = moveList[move - 1][0] - AAA;
15103         fromY = moveList[move - 1][1] - ONE;
15104         toX = moveList[move - 1][2] - AAA;
15105         toY = moveList[move - 1][3] - ONE;
15106         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15107             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15108             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15109             fromX == toX) {
15110             /* 2-square pawn move just happened */
15111             *p++ = toX + AAA;
15112             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15113         } else {
15114             *p++ = '-';
15115         }
15116     } else if(move == backwardMostMove) {
15117         // [HGM] perhaps we should always do it like this, and forget the above?
15118         if((signed char)boards[move][EP_STATUS] >= 0) {
15119             *p++ = boards[move][EP_STATUS] + AAA;
15120             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15121         } else {
15122             *p++ = '-';
15123         }
15124     } else {
15125         *p++ = '-';
15126     }
15127     *p++ = ' ';
15128   }
15129   }
15130
15131     /* [HGM] find reversible plies */
15132     {   int i = 0, j=move;
15133
15134         if (appData.debugMode) { int k;
15135             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15136             for(k=backwardMostMove; k<=forwardMostMove; k++)
15137                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15138
15139         }
15140
15141         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15142         if( j == backwardMostMove ) i += initialRulePlies;
15143         sprintf(p, "%d ", i);
15144         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15145     }
15146     /* Fullmove number */
15147     sprintf(p, "%d", (move / 2) + 1);
15148
15149     return StrSave(buf);
15150 }
15151
15152 Boolean
15153 ParseFEN(board, blackPlaysFirst, fen)
15154     Board board;
15155      int *blackPlaysFirst;
15156      char *fen;
15157 {
15158     int i, j;
15159     char *p, c;
15160     int emptycount;
15161     ChessSquare piece;
15162
15163     p = fen;
15164
15165     /* [HGM] by default clear Crazyhouse holdings, if present */
15166     if(gameInfo.holdingsWidth) {
15167        for(i=0; i<BOARD_HEIGHT; i++) {
15168            board[i][0]             = EmptySquare; /* black holdings */
15169            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15170            board[i][1]             = (ChessSquare) 0; /* black counts */
15171            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15172        }
15173     }
15174
15175     /* Piece placement data */
15176     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15177         j = 0;
15178         for (;;) {
15179             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15180                 if (*p == '/') p++;
15181                 emptycount = gameInfo.boardWidth - j;
15182                 while (emptycount--)
15183                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15184                 break;
15185 #if(BOARD_FILES >= 10)
15186             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15187                 p++; emptycount=10;
15188                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15189                 while (emptycount--)
15190                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15191 #endif
15192             } else if (isdigit(*p)) {
15193                 emptycount = *p++ - '0';
15194                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15195                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15196                 while (emptycount--)
15197                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15198             } else if (*p == '+' || isalpha(*p)) {
15199                 if (j >= gameInfo.boardWidth) return FALSE;
15200                 if(*p=='+') {
15201                     piece = CharToPiece(*++p);
15202                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15203                     piece = (ChessSquare) (PROMOTED piece ); p++;
15204                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15205                 } else piece = CharToPiece(*p++);
15206
15207                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15208                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15209                     piece = (ChessSquare) (PROMOTED piece);
15210                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15211                     p++;
15212                 }
15213                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15214             } else {
15215                 return FALSE;
15216             }
15217         }
15218     }
15219     while (*p == '/' || *p == ' ') p++;
15220
15221     /* [HGM] look for Crazyhouse holdings here */
15222     while(*p==' ') p++;
15223     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15224         if(*p == '[') p++;
15225         if(*p == '-' ) p++; /* empty holdings */ else {
15226             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15227             /* if we would allow FEN reading to set board size, we would   */
15228             /* have to add holdings and shift the board read so far here   */
15229             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15230                 p++;
15231                 if((int) piece >= (int) BlackPawn ) {
15232                     i = (int)piece - (int)BlackPawn;
15233                     i = PieceToNumber((ChessSquare)i);
15234                     if( i >= gameInfo.holdingsSize ) return FALSE;
15235                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15236                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15237                 } else {
15238                     i = (int)piece - (int)WhitePawn;
15239                     i = PieceToNumber((ChessSquare)i);
15240                     if( i >= gameInfo.holdingsSize ) return FALSE;
15241                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15242                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15243                 }
15244             }
15245         }
15246         if(*p == ']') p++;
15247     }
15248
15249     while(*p == ' ') p++;
15250
15251     /* Active color */
15252     c = *p++;
15253     if(appData.colorNickNames) {
15254       if( c == appData.colorNickNames[0] ) c = 'w'; else
15255       if( c == appData.colorNickNames[1] ) c = 'b';
15256     }
15257     switch (c) {
15258       case 'w':
15259         *blackPlaysFirst = FALSE;
15260         break;
15261       case 'b':
15262         *blackPlaysFirst = TRUE;
15263         break;
15264       default:
15265         return FALSE;
15266     }
15267
15268     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15269     /* return the extra info in global variiables             */
15270
15271     /* set defaults in case FEN is incomplete */
15272     board[EP_STATUS] = EP_UNKNOWN;
15273     for(i=0; i<nrCastlingRights; i++ ) {
15274         board[CASTLING][i] =
15275             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15276     }   /* assume possible unless obviously impossible */
15277     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15278     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15279     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15280                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15281     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15282     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15283     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15284                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15285     FENrulePlies = 0;
15286
15287     while(*p==' ') p++;
15288     if(nrCastlingRights) {
15289       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15290           /* castling indicator present, so default becomes no castlings */
15291           for(i=0; i<nrCastlingRights; i++ ) {
15292                  board[CASTLING][i] = NoRights;
15293           }
15294       }
15295       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15296              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15297              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15298              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15299         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15300
15301         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15302             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15303             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15304         }
15305         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15306             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15307         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15308                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15309         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15310                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15311         switch(c) {
15312           case'K':
15313               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15314               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15315               board[CASTLING][2] = whiteKingFile;
15316               break;
15317           case'Q':
15318               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15319               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15320               board[CASTLING][2] = whiteKingFile;
15321               break;
15322           case'k':
15323               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15324               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15325               board[CASTLING][5] = blackKingFile;
15326               break;
15327           case'q':
15328               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15329               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15330               board[CASTLING][5] = blackKingFile;
15331           case '-':
15332               break;
15333           default: /* FRC castlings */
15334               if(c >= 'a') { /* black rights */
15335                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15336                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15337                   if(i == BOARD_RGHT) break;
15338                   board[CASTLING][5] = i;
15339                   c -= AAA;
15340                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15341                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15342                   if(c > i)
15343                       board[CASTLING][3] = c;
15344                   else
15345                       board[CASTLING][4] = c;
15346               } else { /* white rights */
15347                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15348                     if(board[0][i] == WhiteKing) break;
15349                   if(i == BOARD_RGHT) break;
15350                   board[CASTLING][2] = i;
15351                   c -= AAA - 'a' + 'A';
15352                   if(board[0][c] >= WhiteKing) break;
15353                   if(c > i)
15354                       board[CASTLING][0] = c;
15355                   else
15356                       board[CASTLING][1] = c;
15357               }
15358         }
15359       }
15360       for(i=0; i<nrCastlingRights; i++)
15361         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15362     if (appData.debugMode) {
15363         fprintf(debugFP, "FEN castling rights:");
15364         for(i=0; i<nrCastlingRights; i++)
15365         fprintf(debugFP, " %d", board[CASTLING][i]);
15366         fprintf(debugFP, "\n");
15367     }
15368
15369       while(*p==' ') p++;
15370     }
15371
15372     /* read e.p. field in games that know e.p. capture */
15373     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15374        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15375       if(*p=='-') {
15376         p++; board[EP_STATUS] = EP_NONE;
15377       } else {
15378          char c = *p++ - AAA;
15379
15380          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15381          if(*p >= '0' && *p <='9') p++;
15382          board[EP_STATUS] = c;
15383       }
15384     }
15385
15386
15387     if(sscanf(p, "%d", &i) == 1) {
15388         FENrulePlies = i; /* 50-move ply counter */
15389         /* (The move number is still ignored)    */
15390     }
15391
15392     return TRUE;
15393 }
15394
15395 void
15396 EditPositionPasteFEN(char *fen)
15397 {
15398   if (fen != NULL) {
15399     Board initial_position;
15400
15401     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15402       DisplayError(_("Bad FEN position in clipboard"), 0);
15403       return ;
15404     } else {
15405       int savedBlackPlaysFirst = blackPlaysFirst;
15406       EditPositionEvent();
15407       blackPlaysFirst = savedBlackPlaysFirst;
15408       CopyBoard(boards[0], initial_position);
15409       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15410       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15411       DisplayBothClocks();
15412       DrawPosition(FALSE, boards[currentMove]);
15413     }
15414   }
15415 }
15416
15417 static char cseq[12] = "\\   ";
15418
15419 Boolean set_cont_sequence(char *new_seq)
15420 {
15421     int len;
15422     Boolean ret;
15423
15424     // handle bad attempts to set the sequence
15425         if (!new_seq)
15426                 return 0; // acceptable error - no debug
15427
15428     len = strlen(new_seq);
15429     ret = (len > 0) && (len < sizeof(cseq));
15430     if (ret)
15431       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15432     else if (appData.debugMode)
15433       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15434     return ret;
15435 }
15436
15437 /*
15438     reformat a source message so words don't cross the width boundary.  internal
15439     newlines are not removed.  returns the wrapped size (no null character unless
15440     included in source message).  If dest is NULL, only calculate the size required
15441     for the dest buffer.  lp argument indicats line position upon entry, and it's
15442     passed back upon exit.
15443 */
15444 int wrap(char *dest, char *src, int count, int width, int *lp)
15445 {
15446     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15447
15448     cseq_len = strlen(cseq);
15449     old_line = line = *lp;
15450     ansi = len = clen = 0;
15451
15452     for (i=0; i < count; i++)
15453     {
15454         if (src[i] == '\033')
15455             ansi = 1;
15456
15457         // if we hit the width, back up
15458         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15459         {
15460             // store i & len in case the word is too long
15461             old_i = i, old_len = len;
15462
15463             // find the end of the last word
15464             while (i && src[i] != ' ' && src[i] != '\n')
15465             {
15466                 i--;
15467                 len--;
15468             }
15469
15470             // word too long?  restore i & len before splitting it
15471             if ((old_i-i+clen) >= width)
15472             {
15473                 i = old_i;
15474                 len = old_len;
15475             }
15476
15477             // extra space?
15478             if (i && src[i-1] == ' ')
15479                 len--;
15480
15481             if (src[i] != ' ' && src[i] != '\n')
15482             {
15483                 i--;
15484                 if (len)
15485                     len--;
15486             }
15487
15488             // now append the newline and continuation sequence
15489             if (dest)
15490                 dest[len] = '\n';
15491             len++;
15492             if (dest)
15493                 strncpy(dest+len, cseq, cseq_len);
15494             len += cseq_len;
15495             line = cseq_len;
15496             clen = cseq_len;
15497             continue;
15498         }
15499
15500         if (dest)
15501             dest[len] = src[i];
15502         len++;
15503         if (!ansi)
15504             line++;
15505         if (src[i] == '\n')
15506             line = 0;
15507         if (src[i] == 'm')
15508             ansi = 0;
15509     }
15510     if (dest && appData.debugMode)
15511     {
15512         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15513             count, width, line, len, *lp);
15514         show_bytes(debugFP, src, count);
15515         fprintf(debugFP, "\ndest: ");
15516         show_bytes(debugFP, dest, len);
15517         fprintf(debugFP, "\n");
15518     }
15519     *lp = dest ? line : old_line;
15520
15521     return len;
15522 }
15523
15524 // [HGM] vari: routines for shelving variations
15525
15526 void
15527 PushTail(int firstMove, int lastMove)
15528 {
15529         int i, j, nrMoves = lastMove - firstMove;
15530
15531         if(appData.icsActive) { // only in local mode
15532                 forwardMostMove = currentMove; // mimic old ICS behavior
15533                 return;
15534         }
15535         if(storedGames >= MAX_VARIATIONS-1) return;
15536
15537         // push current tail of game on stack
15538         savedResult[storedGames] = gameInfo.result;
15539         savedDetails[storedGames] = gameInfo.resultDetails;
15540         gameInfo.resultDetails = NULL;
15541         savedFirst[storedGames] = firstMove;
15542         savedLast [storedGames] = lastMove;
15543         savedFramePtr[storedGames] = framePtr;
15544         framePtr -= nrMoves; // reserve space for the boards
15545         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15546             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15547             for(j=0; j<MOVE_LEN; j++)
15548                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15549             for(j=0; j<2*MOVE_LEN; j++)
15550                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15551             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15552             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15553             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15554             pvInfoList[firstMove+i-1].depth = 0;
15555             commentList[framePtr+i] = commentList[firstMove+i];
15556             commentList[firstMove+i] = NULL;
15557         }
15558
15559         storedGames++;
15560         forwardMostMove = firstMove; // truncate game so we can start variation
15561         if(storedGames == 1) GreyRevert(FALSE);
15562 }
15563
15564 Boolean
15565 PopTail(Boolean annotate)
15566 {
15567         int i, j, nrMoves;
15568         char buf[8000], moveBuf[20];
15569
15570         if(appData.icsActive) return FALSE; // only in local mode
15571         if(!storedGames) return FALSE; // sanity
15572         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15573
15574         storedGames--;
15575         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15576         nrMoves = savedLast[storedGames] - currentMove;
15577         if(annotate) {
15578                 int cnt = 10;
15579                 if(!WhiteOnMove(currentMove))
15580                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15581                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15582                 for(i=currentMove; i<forwardMostMove; i++) {
15583                         if(WhiteOnMove(i))
15584                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15585                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15586                         strcat(buf, moveBuf);
15587                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15588                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15589                 }
15590                 strcat(buf, ")");
15591         }
15592         for(i=1; i<=nrMoves; i++) { // copy last variation back
15593             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15594             for(j=0; j<MOVE_LEN; j++)
15595                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15596             for(j=0; j<2*MOVE_LEN; j++)
15597                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15598             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15599             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15600             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15601             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15602             commentList[currentMove+i] = commentList[framePtr+i];
15603             commentList[framePtr+i] = NULL;
15604         }
15605         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15606         framePtr = savedFramePtr[storedGames];
15607         gameInfo.result = savedResult[storedGames];
15608         if(gameInfo.resultDetails != NULL) {
15609             free(gameInfo.resultDetails);
15610       }
15611         gameInfo.resultDetails = savedDetails[storedGames];
15612         forwardMostMove = currentMove + nrMoves;
15613         if(storedGames == 0) GreyRevert(TRUE);
15614         return TRUE;
15615 }
15616
15617 void
15618 CleanupTail()
15619 {       // remove all shelved variations
15620         int i;
15621         for(i=0; i<storedGames; i++) {
15622             if(savedDetails[i])
15623                 free(savedDetails[i]);
15624             savedDetails[i] = NULL;
15625         }
15626         for(i=framePtr; i<MAX_MOVES; i++) {
15627                 if(commentList[i]) free(commentList[i]);
15628                 commentList[i] = NULL;
15629         }
15630         framePtr = MAX_MOVES-1;
15631         storedGames = 0;
15632 }
15633
15634 void
15635 LoadVariation(int index, char *text)
15636 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15637         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15638         int level = 0, move;
15639
15640         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15641         // first find outermost bracketing variation
15642         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15643             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15644                 if(*p == '{') wait = '}'; else
15645                 if(*p == '[') wait = ']'; else
15646                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15647                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15648             }
15649             if(*p == wait) wait = NULLCHAR; // closing ]} found
15650             p++;
15651         }
15652         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15653         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15654         end[1] = NULLCHAR; // clip off comment beyond variation
15655         ToNrEvent(currentMove-1);
15656         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15657         // kludge: use ParsePV() to append variation to game
15658         move = currentMove;
15659         ParsePV(start, TRUE);
15660         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15661         ClearPremoveHighlights();
15662         CommentPopDown();
15663         ToNrEvent(currentMove+1);
15664 }
15665