Implement sweep selection as alternative for the piece menu
[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
272 /* States for ics_getting_history */
273 #define H_FALSE 0
274 #define H_REQUESTED 1
275 #define H_GOT_REQ_HEADER 2
276 #define H_GOT_UNREQ_HEADER 3
277 #define H_GETTING_MOVES 4
278 #define H_GOT_UNWANTED_HEADER 5
279
280 /* whosays values for GameEnds */
281 #define GE_ICS 0
282 #define GE_ENGINE 1
283 #define GE_PLAYER 2
284 #define GE_FILE 3
285 #define GE_XBOARD 4
286 #define GE_ENGINE1 5
287 #define GE_ENGINE2 6
288
289 /* Maximum number of games in a cmail message */
290 #define CMAIL_MAX_GAMES 20
291
292 /* Different types of move when calling RegisterMove */
293 #define CMAIL_MOVE   0
294 #define CMAIL_RESIGN 1
295 #define CMAIL_DRAW   2
296 #define CMAIL_ACCEPT 3
297
298 /* Different types of result to remember for each game */
299 #define CMAIL_NOT_RESULT 0
300 #define CMAIL_OLD_RESULT 1
301 #define CMAIL_NEW_RESULT 2
302
303 /* Telnet protocol constants */
304 #define TN_WILL 0373
305 #define TN_WONT 0374
306 #define TN_DO   0375
307 #define TN_DONT 0376
308 #define TN_IAC  0377
309 #define TN_ECHO 0001
310 #define TN_SGA  0003
311 #define TN_PORT 23
312
313 char*
314 safeStrCpy( char *dst, const char *src, size_t count )
315 { // [HGM] made safe
316   int i;
317   assert( dst != NULL );
318   assert( src != NULL );
319   assert( count > 0 );
320
321   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
322   if(  i == count && dst[count-1] != NULLCHAR)
323     {
324       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
325       if(appData.debugMode)
326       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
327     }
328
329   return dst;
330 }
331
332 /* Some compiler can't cast u64 to double
333  * This function do the job for us:
334
335  * We use the highest bit for cast, this only
336  * works if the highest bit is not
337  * in use (This should not happen)
338  *
339  * We used this for all compiler
340  */
341 double
342 u64ToDouble(u64 value)
343 {
344   double r;
345   u64 tmp = value & u64Const(0x7fffffffffffffff);
346   r = (double)(s64)tmp;
347   if (value & u64Const(0x8000000000000000))
348        r +=  9.2233720368547758080e18; /* 2^63 */
349  return r;
350 }
351
352 /* Fake up flags for now, as we aren't keeping track of castling
353    availability yet. [HGM] Change of logic: the flag now only
354    indicates the type of castlings allowed by the rule of the game.
355    The actual rights themselves are maintained in the array
356    castlingRights, as part of the game history, and are not probed
357    by this function.
358  */
359 int
360 PosFlags(index)
361 {
362   int flags = F_ALL_CASTLE_OK;
363   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
364   switch (gameInfo.variant) {
365   case VariantSuicide:
366     flags &= ~F_ALL_CASTLE_OK;
367   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
368     flags |= F_IGNORE_CHECK;
369   case VariantLosers:
370     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
371     break;
372   case VariantAtomic:
373     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
374     break;
375   case VariantKriegspiel:
376     flags |= F_KRIEGSPIEL_CAPTURE;
377     break;
378   case VariantCapaRandom:
379   case VariantFischeRandom:
380     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
381   case VariantNoCastle:
382   case VariantShatranj:
383   case VariantCourier:
384   case VariantMakruk:
385     flags &= ~F_ALL_CASTLE_OK;
386     break;
387   default:
388     break;
389   }
390   return flags;
391 }
392
393 FILE *gameFileFP, *debugFP;
394
395 /*
396     [AS] Note: sometimes, the sscanf() function is used to parse the input
397     into a fixed-size buffer. Because of this, we must be prepared to
398     receive strings as long as the size of the input buffer, which is currently
399     set to 4K for Windows and 8K for the rest.
400     So, we must either allocate sufficiently large buffers here, or
401     reduce the size of the input buffer in the input reading part.
402 */
403
404 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
405 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
406 char thinkOutput1[MSG_SIZ*10];
407
408 ChessProgramState first, second;
409
410 /* premove variables */
411 int premoveToX = 0;
412 int premoveToY = 0;
413 int premoveFromX = 0;
414 int premoveFromY = 0;
415 int premovePromoChar = 0;
416 int gotPremove = 0;
417 Boolean alarmSounded;
418 /* end premove variables */
419
420 char *ics_prefix = "$";
421 int ics_type = ICS_GENERIC;
422
423 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
424 int pauseExamForwardMostMove = 0;
425 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
426 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
427 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
428 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
429 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
430 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
431 int whiteFlag = FALSE, blackFlag = FALSE;
432 int userOfferedDraw = FALSE;
433 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
434 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
435 int cmailMoveType[CMAIL_MAX_GAMES];
436 long ics_clock_paused = 0;
437 ProcRef icsPR = NoProc, cmailPR = NoProc;
438 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
439 GameMode gameMode = BeginningOfGame;
440 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
441 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
442 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
443 int hiddenThinkOutputState = 0; /* [AS] */
444 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
445 int adjudicateLossPlies = 6;
446 char white_holding[64], black_holding[64];
447 TimeMark lastNodeCountTime;
448 long lastNodeCount=0;
449 int shiftKey; // [HGM] set by mouse handler
450
451 int have_sent_ICS_logon = 0;
452 int movesPerSession;
453 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
454 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
455 long timeControl_2; /* [AS] Allow separate time controls */
456 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
457 long timeRemaining[2][MAX_MOVES];
458 int matchGame = 0;
459 TimeMark programStartTime;
460 char ics_handle[MSG_SIZ];
461 int have_set_title = 0;
462
463 /* animateTraining preserves the state of appData.animate
464  * when Training mode is activated. This allows the
465  * response to be animated when appData.animate == TRUE and
466  * appData.animateDragging == TRUE.
467  */
468 Boolean animateTraining;
469
470 GameInfo gameInfo;
471
472 AppData appData;
473
474 Board boards[MAX_MOVES];
475 /* [HGM] Following 7 needed for accurate legality tests: */
476 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
477 signed char  initialRights[BOARD_FILES];
478 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
479 int   initialRulePlies, FENrulePlies;
480 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
481 int loadFlag = 0;
482 int shuffleOpenings;
483 int mute; // mute all sounds
484
485 // [HGM] vari: next 12 to save and restore variations
486 #define MAX_VARIATIONS 10
487 int framePtr = MAX_MOVES-1; // points to free stack entry
488 int storedGames = 0;
489 int savedFirst[MAX_VARIATIONS];
490 int savedLast[MAX_VARIATIONS];
491 int savedFramePtr[MAX_VARIATIONS];
492 char *savedDetails[MAX_VARIATIONS];
493 ChessMove savedResult[MAX_VARIATIONS];
494
495 void PushTail P((int firstMove, int lastMove));
496 Boolean PopTail P((Boolean annotate));
497 void CleanupTail P((void));
498
499 ChessSquare  FIDEArray[2][BOARD_FILES] = {
500     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
501         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
502     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
503         BlackKing, BlackBishop, BlackKnight, BlackRook }
504 };
505
506 ChessSquare twoKingsArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackKing, BlackKnight, BlackRook }
511 };
512
513 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
515         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
516     { BlackRook, BlackMan, BlackBishop, BlackQueen,
517         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
518 };
519
520 ChessSquare SpartanArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
523     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
524         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
525 };
526
527 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
531         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
532 };
533
534 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
535     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
536         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
538         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
539 };
540
541 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
543         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackMan, BlackFerz,
545         BlackKing, BlackMan, BlackKnight, BlackRook }
546 };
547
548
549 #if (BOARD_FILES>=10)
550 ChessSquare ShogiArray[2][BOARD_FILES] = {
551     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
552         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
553     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
554         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
555 };
556
557 ChessSquare XiangqiArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
559         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
561         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
562 };
563
564 ChessSquare CapablancaArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
568         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
569 };
570
571 ChessSquare GreatArray[2][BOARD_FILES] = {
572     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
573         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
574     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
575         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
576 };
577
578 ChessSquare JanusArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
580         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
581     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
582         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
583 };
584
585 #ifdef GOTHIC
586 ChessSquare GothicArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
588         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
590         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
591 };
592 #else // !GOTHIC
593 #define GothicArray CapablancaArray
594 #endif // !GOTHIC
595
596 #ifdef FALCON
597 ChessSquare FalconArray[2][BOARD_FILES] = {
598     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
599         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
600     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
601         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
602 };
603 #else // !FALCON
604 #define FalconArray CapablancaArray
605 #endif // !FALCON
606
607 #else // !(BOARD_FILES>=10)
608 #define XiangqiPosition FIDEArray
609 #define CapablancaArray FIDEArray
610 #define GothicArray FIDEArray
611 #define GreatArray FIDEArray
612 #endif // !(BOARD_FILES>=10)
613
614 #if (BOARD_FILES>=12)
615 ChessSquare CourierArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
617         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
619         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
620 };
621 #else // !(BOARD_FILES>=12)
622 #define CourierArray CapablancaArray
623 #endif // !(BOARD_FILES>=12)
624
625
626 Board initialPosition;
627
628
629 /* Convert str to a rating. Checks for special cases of "----",
630
631    "++++", etc. Also strips ()'s */
632 int
633 string_to_rating(str)
634   char *str;
635 {
636   while(*str && !isdigit(*str)) ++str;
637   if (!*str)
638     return 0;   /* One of the special "no rating" cases */
639   else
640     return atoi(str);
641 }
642
643 void
644 ClearProgramStats()
645 {
646     /* Init programStats */
647     programStats.movelist[0] = 0;
648     programStats.depth = 0;
649     programStats.nr_moves = 0;
650     programStats.moves_left = 0;
651     programStats.nodes = 0;
652     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
653     programStats.score = 0;
654     programStats.got_only_move = 0;
655     programStats.got_fail = 0;
656     programStats.line_is_book = 0;
657 }
658
659 void
660 InitBackEnd1()
661 {
662     int matched, min, sec;
663
664     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
665     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
666
667     GetTimeMark(&programStartTime);
668     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
669
670     ClearProgramStats();
671     programStats.ok_to_send = 1;
672     programStats.seen_stat = 0;
673
674     /*
675      * Initialize game list
676      */
677     ListNew(&gameList);
678
679
680     /*
681      * Internet chess server status
682      */
683     if (appData.icsActive) {
684         appData.matchMode = FALSE;
685         appData.matchGames = 0;
686 #if ZIPPY
687         appData.noChessProgram = !appData.zippyPlay;
688 #else
689         appData.zippyPlay = FALSE;
690         appData.zippyTalk = FALSE;
691         appData.noChessProgram = TRUE;
692 #endif
693         if (*appData.icsHelper != NULLCHAR) {
694             appData.useTelnet = TRUE;
695             appData.telnetProgram = appData.icsHelper;
696         }
697     } else {
698         appData.zippyTalk = appData.zippyPlay = FALSE;
699     }
700
701     /* [AS] Initialize pv info list [HGM] and game state */
702     {
703         int i, j;
704
705         for( i=0; i<=framePtr; i++ ) {
706             pvInfoList[i].depth = -1;
707             boards[i][EP_STATUS] = EP_NONE;
708             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
709         }
710     }
711
712     /*
713      * Parse timeControl resource
714      */
715     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
716                           appData.movesPerSession)) {
717         char buf[MSG_SIZ];
718         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
719         DisplayFatalError(buf, 0, 2);
720     }
721
722     /*
723      * Parse searchTime resource
724      */
725     if (*appData.searchTime != NULLCHAR) {
726         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
727         if (matched == 1) {
728             searchTime = min * 60;
729         } else if (matched == 2) {
730             searchTime = min * 60 + sec;
731         } else {
732             char buf[MSG_SIZ];
733             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
734             DisplayFatalError(buf, 0, 2);
735         }
736     }
737
738     /* [AS] Adjudication threshold */
739     adjudicateLossThreshold = appData.adjudicateLossThreshold;
740
741     first.which = "first";
742     second.which = "second";
743     first.maybeThinking = second.maybeThinking = FALSE;
744     first.pr = second.pr = NoProc;
745     first.isr = second.isr = NULL;
746     first.sendTime = second.sendTime = 2;
747     first.sendDrawOffers = 1;
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755     first.program = appData.firstChessProgram;
756     second.program = appData.secondChessProgram;
757     first.host = appData.firstHost;
758     second.host = appData.secondHost;
759     first.dir = appData.firstDirectory;
760     second.dir = appData.secondDirectory;
761     first.other = &second;
762     second.other = &first;
763     first.initString = appData.initString;
764     second.initString = appData.secondInitString;
765     first.computerString = appData.firstComputerString;
766     second.computerString = appData.secondComputerString;
767     first.useSigint = second.useSigint = TRUE;
768     first.useSigterm = second.useSigterm = TRUE;
769     first.reuse = appData.reuseFirst;
770     second.reuse = appData.reuseSecond;
771     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
772     second.nps = appData.secondNPS;
773     first.useSetboard = second.useSetboard = FALSE;
774     first.useSAN = second.useSAN = FALSE;
775     first.usePing = second.usePing = FALSE;
776     first.lastPing = second.lastPing = 0;
777     first.lastPong = second.lastPong = 0;
778     first.usePlayother = second.usePlayother = FALSE;
779     first.useColors = second.useColors = TRUE;
780     first.useUsermove = second.useUsermove = FALSE;
781     first.sendICS = second.sendICS = FALSE;
782     first.sendName = second.sendName = appData.icsActive;
783     first.sdKludge = second.sdKludge = FALSE;
784     first.stKludge = second.stKludge = FALSE;
785     TidyProgramName(first.program, first.host, first.tidy);
786     TidyProgramName(second.program, second.host, second.tidy);
787     first.matchWins = second.matchWins = 0;
788     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
789     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
790     first.analysisSupport = second.analysisSupport = 2; /* detect */
791     first.analyzing = second.analyzing = FALSE;
792     first.initDone = second.initDone = FALSE;
793
794     /* New features added by Tord: */
795     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
796     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
797     /* End of new features added by Tord. */
798     first.fenOverride  = appData.fenOverride1;
799     second.fenOverride = appData.fenOverride2;
800
801     /* [HGM] time odds: set factor for each machine */
802     first.timeOdds  = appData.firstTimeOdds;
803     second.timeOdds = appData.secondTimeOdds;
804     { float norm = 1;
805         if(appData.timeOddsMode) {
806             norm = first.timeOdds;
807             if(norm > second.timeOdds) norm = second.timeOdds;
808         }
809         first.timeOdds /= norm;
810         second.timeOdds /= norm;
811     }
812
813     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
814     first.accumulateTC = appData.firstAccumulateTC;
815     second.accumulateTC = appData.secondAccumulateTC;
816     first.maxNrOfSessions = second.maxNrOfSessions = 1;
817
818     /* [HGM] debug */
819     first.debug = second.debug = FALSE;
820     first.supportsNPS = second.supportsNPS = UNKNOWN;
821
822     /* [HGM] options */
823     first.optionSettings  = appData.firstOptions;
824     second.optionSettings = appData.secondOptions;
825
826     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
827     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
828     first.isUCI = appData.firstIsUCI; /* [AS] */
829     second.isUCI = appData.secondIsUCI; /* [AS] */
830     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
831     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
832
833     if (appData.firstProtocolVersion > PROTOVER
834         || appData.firstProtocolVersion < 1)
835       {
836         char buf[MSG_SIZ];
837         int len;
838
839         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
840                        appData.firstProtocolVersion);
841         if( (len > MSG_SIZ) && appData.debugMode )
842           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
843
844         DisplayFatalError(buf, 0, 2);
845       }
846     else
847       {
848         first.protocolVersion = appData.firstProtocolVersion;
849       }
850
851     if (appData.secondProtocolVersion > PROTOVER
852         || appData.secondProtocolVersion < 1)
853       {
854         char buf[MSG_SIZ];
855         int len;
856
857         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
858                        appData.secondProtocolVersion);
859         if( (len > MSG_SIZ) && appData.debugMode )
860           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
861
862         DisplayFatalError(buf, 0, 2);
863       }
864     else
865       {
866         second.protocolVersion = appData.secondProtocolVersion;
867       }
868
869     if (appData.icsActive) {
870         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
871 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
872     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
873         appData.clockMode = FALSE;
874         first.sendTime = second.sendTime = 0;
875     }
876
877 #if ZIPPY
878     /* Override some settings from environment variables, for backward
879        compatibility.  Unfortunately it's not feasible to have the env
880        vars just set defaults, at least in xboard.  Ugh.
881     */
882     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
883       ZippyInit();
884     }
885 #endif
886
887     if (appData.noChessProgram) {
888         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
889         sprintf(programVersion, "%s", PACKAGE_STRING);
890     } else {
891       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
892       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
893       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
894     }
895
896     if (!appData.icsActive) {
897       char buf[MSG_SIZ];
898       int len;
899
900       /* Check for variants that are supported only in ICS mode,
901          or not at all.  Some that are accepted here nevertheless
902          have bugs; see comments below.
903       */
904       VariantClass variant = StringToVariant(appData.variant);
905       switch (variant) {
906       case VariantBughouse:     /* need four players and two boards */
907       case VariantKriegspiel:   /* need to hide pieces and move details */
908         /* case VariantFischeRandom: (Fabien: moved below) */
909         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
910         if( (len > MSG_SIZ) && appData.debugMode )
911           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
912
913         DisplayFatalError(buf, 0, 2);
914         return;
915
916       case VariantUnknown:
917       case VariantLoadable:
918       case Variant29:
919       case Variant30:
920       case Variant31:
921       case Variant32:
922       case Variant33:
923       case Variant34:
924       case Variant35:
925       case Variant36:
926       default:
927         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
928         if( (len > MSG_SIZ) && appData.debugMode )
929           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
930
931         DisplayFatalError(buf, 0, 2);
932         return;
933
934       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
935       case VariantFairy:      /* [HGM] TestLegality definitely off! */
936       case VariantGothic:     /* [HGM] should work */
937       case VariantCapablanca: /* [HGM] should work */
938       case VariantCourier:    /* [HGM] initial forced moves not implemented */
939       case VariantShogi:      /* [HGM] could still mate with pawn drop */
940       case VariantKnightmate: /* [HGM] should work */
941       case VariantCylinder:   /* [HGM] untested */
942       case VariantFalcon:     /* [HGM] untested */
943       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
944                                  offboard interposition not understood */
945       case VariantNormal:     /* definitely works! */
946       case VariantWildCastle: /* pieces not automatically shuffled */
947       case VariantNoCastle:   /* pieces not automatically shuffled */
948       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
949       case VariantLosers:     /* should work except for win condition,
950                                  and doesn't know captures are mandatory */
951       case VariantSuicide:    /* should work except for win condition,
952                                  and doesn't know captures are mandatory */
953       case VariantGiveaway:   /* should work except for win condition,
954                                  and doesn't know captures are mandatory */
955       case VariantTwoKings:   /* should work */
956       case VariantAtomic:     /* should work except for win condition */
957       case Variant3Check:     /* should work except for win condition */
958       case VariantShatranj:   /* should work except for all win conditions */
959       case VariantMakruk:     /* should work except for daw countdown */
960       case VariantBerolina:   /* might work if TestLegality is off */
961       case VariantCapaRandom: /* should work */
962       case VariantJanus:      /* should work */
963       case VariantSuper:      /* experimental */
964       case VariantGreat:      /* experimental, requires legality testing to be off */
965       case VariantSChess:     /* S-Chess, should work */
966       case VariantSpartan:    /* should work */
967         break;
968       }
969     }
970
971     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
972     InitEngineUCI( installDir, &second );
973 }
974
975 int NextIntegerFromString( char ** str, long * value )
976 {
977     int result = -1;
978     char * s = *str;
979
980     while( *s == ' ' || *s == '\t' ) {
981         s++;
982     }
983
984     *value = 0;
985
986     if( *s >= '0' && *s <= '9' ) {
987         while( *s >= '0' && *s <= '9' ) {
988             *value = *value * 10 + (*s - '0');
989             s++;
990         }
991
992         result = 0;
993     }
994
995     *str = s;
996
997     return result;
998 }
999
1000 int NextTimeControlFromString( char ** str, long * value )
1001 {
1002     long temp;
1003     int result = NextIntegerFromString( str, &temp );
1004
1005     if( result == 0 ) {
1006         *value = temp * 60; /* Minutes */
1007         if( **str == ':' ) {
1008             (*str)++;
1009             result = NextIntegerFromString( str, &temp );
1010             *value += temp; /* Seconds */
1011         }
1012     }
1013
1014     return result;
1015 }
1016
1017 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1018 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1019     int result = -1, type = 0; long temp, temp2;
1020
1021     if(**str != ':') return -1; // old params remain in force!
1022     (*str)++;
1023     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1024     if( NextIntegerFromString( str, &temp ) ) return -1;
1025     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1026
1027     if(**str != '/') {
1028         /* time only: incremental or sudden-death time control */
1029         if(**str == '+') { /* increment follows; read it */
1030             (*str)++;
1031             if(**str == '!') type = *(*str)++; // Bronstein TC
1032             if(result = NextIntegerFromString( str, &temp2)) return -1;
1033             *inc = temp2 * 1000;
1034             if(**str == '.') { // read fraction of increment
1035                 char *start = ++(*str);
1036                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1037                 temp2 *= 1000;
1038                 while(start++ < *str) temp2 /= 10;
1039                 *inc += temp2;
1040             }
1041         } else *inc = 0;
1042         *moves = 0; *tc = temp * 1000; *incType = type;
1043         return 0;
1044     }
1045
1046     (*str)++; /* classical time control */
1047     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1048
1049     if(result == 0) {
1050         *moves = temp;
1051         *tc    = temp2 * 1000;
1052         *inc   = 0;
1053         *incType = type;
1054     }
1055     return result;
1056 }
1057
1058 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1059 {   /* [HGM] get time to add from the multi-session time-control string */
1060     int incType, moves=1; /* kludge to force reading of first session */
1061     long time, increment;
1062     char *s = tcString;
1063
1064     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1065     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1066     do {
1067         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1068         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1069         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1070         if(movenr == -1) return time;    /* last move before new session     */
1071         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1072         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1073         if(!moves) return increment;     /* current session is incremental   */
1074         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1075     } while(movenr >= -1);               /* try again for next session       */
1076
1077     return 0; // no new time quota on this move
1078 }
1079
1080 int
1081 ParseTimeControl(tc, ti, mps)
1082      char *tc;
1083      float ti;
1084      int mps;
1085 {
1086   long tc1;
1087   long tc2;
1088   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1089   int min, sec=0;
1090
1091   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1092   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1093       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1094   if(ti > 0) {
1095
1096     if(mps)
1097       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1098     else 
1099       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1100   } else {
1101     if(mps)
1102       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1103     else 
1104       snprintf(buf, MSG_SIZ, ":%s", mytc);
1105   }
1106   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1107   
1108   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1109     return FALSE;
1110   }
1111
1112   if( *tc == '/' ) {
1113     /* Parse second time control */
1114     tc++;
1115
1116     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1117       return FALSE;
1118     }
1119
1120     if( tc2 == 0 ) {
1121       return FALSE;
1122     }
1123
1124     timeControl_2 = tc2 * 1000;
1125   }
1126   else {
1127     timeControl_2 = 0;
1128   }
1129
1130   if( tc1 == 0 ) {
1131     return FALSE;
1132   }
1133
1134   timeControl = tc1 * 1000;
1135
1136   if (ti >= 0) {
1137     timeIncrement = ti * 1000;  /* convert to ms */
1138     movesPerSession = 0;
1139   } else {
1140     timeIncrement = 0;
1141     movesPerSession = mps;
1142   }
1143   return TRUE;
1144 }
1145
1146 void
1147 InitBackEnd2()
1148 {
1149     if (appData.debugMode) {
1150         fprintf(debugFP, "%s\n", programVersion);
1151     }
1152
1153     set_cont_sequence(appData.wrapContSeq);
1154     if (appData.matchGames > 0) {
1155         appData.matchMode = TRUE;
1156     } else if (appData.matchMode) {
1157         appData.matchGames = 1;
1158     }
1159     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1160         appData.matchGames = appData.sameColorGames;
1161     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1162         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1163         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1164     }
1165     Reset(TRUE, FALSE);
1166     if (appData.noChessProgram || first.protocolVersion == 1) {
1167       InitBackEnd3();
1168     } else {
1169       /* kludge: allow timeout for initial "feature" commands */
1170       FreezeUI();
1171       DisplayMessage("", _("Starting chess program"));
1172       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1173     }
1174 }
1175
1176 void
1177 InitBackEnd3 P((void))
1178 {
1179     GameMode initialMode;
1180     char buf[MSG_SIZ];
1181     int err, len;
1182
1183     InitChessProgram(&first, startedFromSetupPosition);
1184
1185     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1186         free(programVersion);
1187         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1188         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1189     }
1190
1191     if (appData.icsActive) {
1192 #ifdef WIN32
1193         /* [DM] Make a console window if needed [HGM] merged ifs */
1194         ConsoleCreate();
1195 #endif
1196         err = establish();
1197         if (err != 0)
1198           {
1199             if (*appData.icsCommPort != NULLCHAR)
1200               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1201                              appData.icsCommPort);
1202             else
1203               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1204                         appData.icsHost, appData.icsPort);
1205
1206             if( (len > MSG_SIZ) && appData.debugMode )
1207               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1208
1209             DisplayFatalError(buf, err, 1);
1210             return;
1211         }
1212         SetICSMode();
1213         telnetISR =
1214           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1215         fromUserISR =
1216           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1217         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1218             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1219     } else if (appData.noChessProgram) {
1220         SetNCPMode();
1221     } else {
1222         SetGNUMode();
1223     }
1224
1225     if (*appData.cmailGameName != NULLCHAR) {
1226         SetCmailMode();
1227         OpenLoopback(&cmailPR);
1228         cmailISR =
1229           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1230     }
1231
1232     ThawUI();
1233     DisplayMessage("", "");
1234     if (StrCaseCmp(appData.initialMode, "") == 0) {
1235       initialMode = BeginningOfGame;
1236     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1237       initialMode = TwoMachinesPlay;
1238     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1239       initialMode = AnalyzeFile;
1240     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1241       initialMode = AnalyzeMode;
1242     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1243       initialMode = MachinePlaysWhite;
1244     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1245       initialMode = MachinePlaysBlack;
1246     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1247       initialMode = EditGame;
1248     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1249       initialMode = EditPosition;
1250     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1251       initialMode = Training;
1252     } else {
1253       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1254       if( (len > MSG_SIZ) && appData.debugMode )
1255         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1256
1257       DisplayFatalError(buf, 0, 2);
1258       return;
1259     }
1260
1261     if (appData.matchMode) {
1262         /* Set up machine vs. machine match */
1263         if (appData.noChessProgram) {
1264             DisplayFatalError(_("Can't have a match with no chess programs"),
1265                               0, 2);
1266             return;
1267         }
1268         matchMode = TRUE;
1269         matchGame = 1;
1270         if (*appData.loadGameFile != NULLCHAR) {
1271             int index = appData.loadGameIndex; // [HGM] autoinc
1272             if(index<0) lastIndex = index = 1;
1273             if (!LoadGameFromFile(appData.loadGameFile,
1274                                   index,
1275                                   appData.loadGameFile, FALSE)) {
1276                 DisplayFatalError(_("Bad game file"), 0, 1);
1277                 return;
1278             }
1279         } else if (*appData.loadPositionFile != NULLCHAR) {
1280             int index = appData.loadPositionIndex; // [HGM] autoinc
1281             if(index<0) lastIndex = index = 1;
1282             if (!LoadPositionFromFile(appData.loadPositionFile,
1283                                       index,
1284                                       appData.loadPositionFile)) {
1285                 DisplayFatalError(_("Bad position file"), 0, 1);
1286                 return;
1287             }
1288         }
1289         TwoMachinesEvent();
1290     } else if (*appData.cmailGameName != NULLCHAR) {
1291         /* Set up cmail mode */
1292         ReloadCmailMsgEvent(TRUE);
1293     } else {
1294         /* Set up other modes */
1295         if (initialMode == AnalyzeFile) {
1296           if (*appData.loadGameFile == NULLCHAR) {
1297             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1298             return;
1299           }
1300         }
1301         if (*appData.loadGameFile != NULLCHAR) {
1302             (void) LoadGameFromFile(appData.loadGameFile,
1303                                     appData.loadGameIndex,
1304                                     appData.loadGameFile, TRUE);
1305         } else if (*appData.loadPositionFile != NULLCHAR) {
1306             (void) LoadPositionFromFile(appData.loadPositionFile,
1307                                         appData.loadPositionIndex,
1308                                         appData.loadPositionFile);
1309             /* [HGM] try to make self-starting even after FEN load */
1310             /* to allow automatic setup of fairy variants with wtm */
1311             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1312                 gameMode = BeginningOfGame;
1313                 setboardSpoiledMachineBlack = 1;
1314             }
1315             /* [HGM] loadPos: make that every new game uses the setup */
1316             /* from file as long as we do not switch variant          */
1317             if(!blackPlaysFirst) {
1318                 startedFromPositionFile = TRUE;
1319                 CopyBoard(filePosition, boards[0]);
1320             }
1321         }
1322         if (initialMode == AnalyzeMode) {
1323           if (appData.noChessProgram) {
1324             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1325             return;
1326           }
1327           if (appData.icsActive) {
1328             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1329             return;
1330           }
1331           AnalyzeModeEvent();
1332         } else if (initialMode == AnalyzeFile) {
1333           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1334           ShowThinkingEvent();
1335           AnalyzeFileEvent();
1336           AnalysisPeriodicEvent(1);
1337         } else if (initialMode == MachinePlaysWhite) {
1338           if (appData.noChessProgram) {
1339             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1340                               0, 2);
1341             return;
1342           }
1343           if (appData.icsActive) {
1344             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1345                               0, 2);
1346             return;
1347           }
1348           MachineWhiteEvent();
1349         } else if (initialMode == MachinePlaysBlack) {
1350           if (appData.noChessProgram) {
1351             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1352                               0, 2);
1353             return;
1354           }
1355           if (appData.icsActive) {
1356             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1357                               0, 2);
1358             return;
1359           }
1360           MachineBlackEvent();
1361         } else if (initialMode == TwoMachinesPlay) {
1362           if (appData.noChessProgram) {
1363             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1364                               0, 2);
1365             return;
1366           }
1367           if (appData.icsActive) {
1368             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1369                               0, 2);
1370             return;
1371           }
1372           TwoMachinesEvent();
1373         } else if (initialMode == EditGame) {
1374           EditGameEvent();
1375         } else if (initialMode == EditPosition) {
1376           EditPositionEvent();
1377         } else if (initialMode == Training) {
1378           if (*appData.loadGameFile == NULLCHAR) {
1379             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1380             return;
1381           }
1382           TrainingEvent();
1383         }
1384     }
1385 }
1386
1387 /*
1388  * Establish will establish a contact to a remote host.port.
1389  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1390  *  used to talk to the host.
1391  * Returns 0 if okay, error code if not.
1392  */
1393 int
1394 establish()
1395 {
1396     char buf[MSG_SIZ];
1397
1398     if (*appData.icsCommPort != NULLCHAR) {
1399         /* Talk to the host through a serial comm port */
1400         return OpenCommPort(appData.icsCommPort, &icsPR);
1401
1402     } else if (*appData.gateway != NULLCHAR) {
1403         if (*appData.remoteShell == NULLCHAR) {
1404             /* Use the rcmd protocol to run telnet program on a gateway host */
1405             snprintf(buf, sizeof(buf), "%s %s %s",
1406                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1407             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1408
1409         } else {
1410             /* Use the rsh program to run telnet program on a gateway host */
1411             if (*appData.remoteUser == NULLCHAR) {
1412                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1413                         appData.gateway, appData.telnetProgram,
1414                         appData.icsHost, appData.icsPort);
1415             } else {
1416                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1417                         appData.remoteShell, appData.gateway,
1418                         appData.remoteUser, appData.telnetProgram,
1419                         appData.icsHost, appData.icsPort);
1420             }
1421             return StartChildProcess(buf, "", &icsPR);
1422
1423         }
1424     } else if (appData.useTelnet) {
1425         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1426
1427     } else {
1428         /* TCP socket interface differs somewhat between
1429            Unix and NT; handle details in the front end.
1430            */
1431         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1432     }
1433 }
1434
1435 void EscapeExpand(char *p, char *q)
1436 {       // [HGM] initstring: routine to shape up string arguments
1437         while(*p++ = *q++) if(p[-1] == '\\')
1438             switch(*q++) {
1439                 case 'n': p[-1] = '\n'; break;
1440                 case 'r': p[-1] = '\r'; break;
1441                 case 't': p[-1] = '\t'; break;
1442                 case '\\': p[-1] = '\\'; break;
1443                 case 0: *p = 0; return;
1444                 default: p[-1] = q[-1]; break;
1445             }
1446 }
1447
1448 void
1449 show_bytes(fp, buf, count)
1450      FILE *fp;
1451      char *buf;
1452      int count;
1453 {
1454     while (count--) {
1455         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1456             fprintf(fp, "\\%03o", *buf & 0xff);
1457         } else {
1458             putc(*buf, fp);
1459         }
1460         buf++;
1461     }
1462     fflush(fp);
1463 }
1464
1465 /* Returns an errno value */
1466 int
1467 OutputMaybeTelnet(pr, message, count, outError)
1468      ProcRef pr;
1469      char *message;
1470      int count;
1471      int *outError;
1472 {
1473     char buf[8192], *p, *q, *buflim;
1474     int left, newcount, outcount;
1475
1476     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1477         *appData.gateway != NULLCHAR) {
1478         if (appData.debugMode) {
1479             fprintf(debugFP, ">ICS: ");
1480             show_bytes(debugFP, message, count);
1481             fprintf(debugFP, "\n");
1482         }
1483         return OutputToProcess(pr, message, count, outError);
1484     }
1485
1486     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1487     p = message;
1488     q = buf;
1489     left = count;
1490     newcount = 0;
1491     while (left) {
1492         if (q >= buflim) {
1493             if (appData.debugMode) {
1494                 fprintf(debugFP, ">ICS: ");
1495                 show_bytes(debugFP, buf, newcount);
1496                 fprintf(debugFP, "\n");
1497             }
1498             outcount = OutputToProcess(pr, buf, newcount, outError);
1499             if (outcount < newcount) return -1; /* to be sure */
1500             q = buf;
1501             newcount = 0;
1502         }
1503         if (*p == '\n') {
1504             *q++ = '\r';
1505             newcount++;
1506         } else if (((unsigned char) *p) == TN_IAC) {
1507             *q++ = (char) TN_IAC;
1508             newcount ++;
1509         }
1510         *q++ = *p++;
1511         newcount++;
1512         left--;
1513     }
1514     if (appData.debugMode) {
1515         fprintf(debugFP, ">ICS: ");
1516         show_bytes(debugFP, buf, newcount);
1517         fprintf(debugFP, "\n");
1518     }
1519     outcount = OutputToProcess(pr, buf, newcount, outError);
1520     if (outcount < newcount) return -1; /* to be sure */
1521     return count;
1522 }
1523
1524 void
1525 read_from_player(isr, closure, message, count, error)
1526      InputSourceRef isr;
1527      VOIDSTAR closure;
1528      char *message;
1529      int count;
1530      int error;
1531 {
1532     int outError, outCount;
1533     static int gotEof = 0;
1534
1535     /* Pass data read from player on to ICS */
1536     if (count > 0) {
1537         gotEof = 0;
1538         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1539         if (outCount < count) {
1540             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1541         }
1542     } else if (count < 0) {
1543         RemoveInputSource(isr);
1544         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1545     } else if (gotEof++ > 0) {
1546         RemoveInputSource(isr);
1547         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1548     }
1549 }
1550
1551 void
1552 KeepAlive()
1553 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1554     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1555     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1556     SendToICS("date\n");
1557     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1558 }
1559
1560 /* added routine for printf style output to ics */
1561 void ics_printf(char *format, ...)
1562 {
1563     char buffer[MSG_SIZ];
1564     va_list args;
1565
1566     va_start(args, format);
1567     vsnprintf(buffer, sizeof(buffer), format, args);
1568     buffer[sizeof(buffer)-1] = '\0';
1569     SendToICS(buffer);
1570     va_end(args);
1571 }
1572
1573 void
1574 SendToICS(s)
1575      char *s;
1576 {
1577     int count, outCount, outError;
1578
1579     if (icsPR == NULL) return;
1580
1581     count = strlen(s);
1582     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1583     if (outCount < count) {
1584         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1585     }
1586 }
1587
1588 /* This is used for sending logon scripts to the ICS. Sending
1589    without a delay causes problems when using timestamp on ICC
1590    (at least on my machine). */
1591 void
1592 SendToICSDelayed(s,msdelay)
1593      char *s;
1594      long msdelay;
1595 {
1596     int count, outCount, outError;
1597
1598     if (icsPR == NULL) return;
1599
1600     count = strlen(s);
1601     if (appData.debugMode) {
1602         fprintf(debugFP, ">ICS: ");
1603         show_bytes(debugFP, s, count);
1604         fprintf(debugFP, "\n");
1605     }
1606     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1607                                       msdelay);
1608     if (outCount < count) {
1609         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1610     }
1611 }
1612
1613
1614 /* Remove all highlighting escape sequences in s
1615    Also deletes any suffix starting with '('
1616    */
1617 char *
1618 StripHighlightAndTitle(s)
1619      char *s;
1620 {
1621     static char retbuf[MSG_SIZ];
1622     char *p = retbuf;
1623
1624     while (*s != NULLCHAR) {
1625         while (*s == '\033') {
1626             while (*s != NULLCHAR && !isalpha(*s)) s++;
1627             if (*s != NULLCHAR) s++;
1628         }
1629         while (*s != NULLCHAR && *s != '\033') {
1630             if (*s == '(' || *s == '[') {
1631                 *p = NULLCHAR;
1632                 return retbuf;
1633             }
1634             *p++ = *s++;
1635         }
1636     }
1637     *p = NULLCHAR;
1638     return retbuf;
1639 }
1640
1641 /* Remove all highlighting escape sequences in s */
1642 char *
1643 StripHighlight(s)
1644      char *s;
1645 {
1646     static char retbuf[MSG_SIZ];
1647     char *p = retbuf;
1648
1649     while (*s != NULLCHAR) {
1650         while (*s == '\033') {
1651             while (*s != NULLCHAR && !isalpha(*s)) s++;
1652             if (*s != NULLCHAR) s++;
1653         }
1654         while (*s != NULLCHAR && *s != '\033') {
1655             *p++ = *s++;
1656         }
1657     }
1658     *p = NULLCHAR;
1659     return retbuf;
1660 }
1661
1662 char *variantNames[] = VARIANT_NAMES;
1663 char *
1664 VariantName(v)
1665      VariantClass v;
1666 {
1667     return variantNames[v];
1668 }
1669
1670
1671 /* Identify a variant from the strings the chess servers use or the
1672    PGN Variant tag names we use. */
1673 VariantClass
1674 StringToVariant(e)
1675      char *e;
1676 {
1677     char *p;
1678     int wnum = -1;
1679     VariantClass v = VariantNormal;
1680     int i, found = FALSE;
1681     char buf[MSG_SIZ];
1682     int len;
1683
1684     if (!e) return v;
1685
1686     /* [HGM] skip over optional board-size prefixes */
1687     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1688         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1689         while( *e++ != '_');
1690     }
1691
1692     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1693         v = VariantNormal;
1694         found = TRUE;
1695     } else
1696     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1697       if (StrCaseStr(e, variantNames[i])) {
1698         v = (VariantClass) i;
1699         found = TRUE;
1700         break;
1701       }
1702     }
1703
1704     if (!found) {
1705       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1706           || StrCaseStr(e, "wild/fr")
1707           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1708         v = VariantFischeRandom;
1709       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1710                  (i = 1, p = StrCaseStr(e, "w"))) {
1711         p += i;
1712         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1713         if (isdigit(*p)) {
1714           wnum = atoi(p);
1715         } else {
1716           wnum = -1;
1717         }
1718         switch (wnum) {
1719         case 0: /* FICS only, actually */
1720         case 1:
1721           /* Castling legal even if K starts on d-file */
1722           v = VariantWildCastle;
1723           break;
1724         case 2:
1725         case 3:
1726         case 4:
1727           /* Castling illegal even if K & R happen to start in
1728              normal positions. */
1729           v = VariantNoCastle;
1730           break;
1731         case 5:
1732         case 7:
1733         case 8:
1734         case 10:
1735         case 11:
1736         case 12:
1737         case 13:
1738         case 14:
1739         case 15:
1740         case 18:
1741         case 19:
1742           /* Castling legal iff K & R start in normal positions */
1743           v = VariantNormal;
1744           break;
1745         case 6:
1746         case 20:
1747         case 21:
1748           /* Special wilds for position setup; unclear what to do here */
1749           v = VariantLoadable;
1750           break;
1751         case 9:
1752           /* Bizarre ICC game */
1753           v = VariantTwoKings;
1754           break;
1755         case 16:
1756           v = VariantKriegspiel;
1757           break;
1758         case 17:
1759           v = VariantLosers;
1760           break;
1761         case 22:
1762           v = VariantFischeRandom;
1763           break;
1764         case 23:
1765           v = VariantCrazyhouse;
1766           break;
1767         case 24:
1768           v = VariantBughouse;
1769           break;
1770         case 25:
1771           v = Variant3Check;
1772           break;
1773         case 26:
1774           /* Not quite the same as FICS suicide! */
1775           v = VariantGiveaway;
1776           break;
1777         case 27:
1778           v = VariantAtomic;
1779           break;
1780         case 28:
1781           v = VariantShatranj;
1782           break;
1783
1784         /* Temporary names for future ICC types.  The name *will* change in
1785            the next xboard/WinBoard release after ICC defines it. */
1786         case 29:
1787           v = Variant29;
1788           break;
1789         case 30:
1790           v = Variant30;
1791           break;
1792         case 31:
1793           v = Variant31;
1794           break;
1795         case 32:
1796           v = Variant32;
1797           break;
1798         case 33:
1799           v = Variant33;
1800           break;
1801         case 34:
1802           v = Variant34;
1803           break;
1804         case 35:
1805           v = Variant35;
1806           break;
1807         case 36:
1808           v = Variant36;
1809           break;
1810         case 37:
1811           v = VariantShogi;
1812           break;
1813         case 38:
1814           v = VariantXiangqi;
1815           break;
1816         case 39:
1817           v = VariantCourier;
1818           break;
1819         case 40:
1820           v = VariantGothic;
1821           break;
1822         case 41:
1823           v = VariantCapablanca;
1824           break;
1825         case 42:
1826           v = VariantKnightmate;
1827           break;
1828         case 43:
1829           v = VariantFairy;
1830           break;
1831         case 44:
1832           v = VariantCylinder;
1833           break;
1834         case 45:
1835           v = VariantFalcon;
1836           break;
1837         case 46:
1838           v = VariantCapaRandom;
1839           break;
1840         case 47:
1841           v = VariantBerolina;
1842           break;
1843         case 48:
1844           v = VariantJanus;
1845           break;
1846         case 49:
1847           v = VariantSuper;
1848           break;
1849         case 50:
1850           v = VariantGreat;
1851           break;
1852         case -1:
1853           /* Found "wild" or "w" in the string but no number;
1854              must assume it's normal chess. */
1855           v = VariantNormal;
1856           break;
1857         default:
1858           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1859           if( (len > MSG_SIZ) && appData.debugMode )
1860             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1861
1862           DisplayError(buf, 0);
1863           v = VariantUnknown;
1864           break;
1865         }
1866       }
1867     }
1868     if (appData.debugMode) {
1869       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1870               e, wnum, VariantName(v));
1871     }
1872     return v;
1873 }
1874
1875 static int leftover_start = 0, leftover_len = 0;
1876 char star_match[STAR_MATCH_N][MSG_SIZ];
1877
1878 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1879    advance *index beyond it, and set leftover_start to the new value of
1880    *index; else return FALSE.  If pattern contains the character '*', it
1881    matches any sequence of characters not containing '\r', '\n', or the
1882    character following the '*' (if any), and the matched sequence(s) are
1883    copied into star_match.
1884    */
1885 int
1886 looking_at(buf, index, pattern)
1887      char *buf;
1888      int *index;
1889      char *pattern;
1890 {
1891     char *bufp = &buf[*index], *patternp = pattern;
1892     int star_count = 0;
1893     char *matchp = star_match[0];
1894
1895     for (;;) {
1896         if (*patternp == NULLCHAR) {
1897             *index = leftover_start = bufp - buf;
1898             *matchp = NULLCHAR;
1899             return TRUE;
1900         }
1901         if (*bufp == NULLCHAR) return FALSE;
1902         if (*patternp == '*') {
1903             if (*bufp == *(patternp + 1)) {
1904                 *matchp = NULLCHAR;
1905                 matchp = star_match[++star_count];
1906                 patternp += 2;
1907                 bufp++;
1908                 continue;
1909             } else if (*bufp == '\n' || *bufp == '\r') {
1910                 patternp++;
1911                 if (*patternp == NULLCHAR)
1912                   continue;
1913                 else
1914                   return FALSE;
1915             } else {
1916                 *matchp++ = *bufp++;
1917                 continue;
1918             }
1919         }
1920         if (*patternp != *bufp) return FALSE;
1921         patternp++;
1922         bufp++;
1923     }
1924 }
1925
1926 void
1927 SendToPlayer(data, length)
1928      char *data;
1929      int length;
1930 {
1931     int error, outCount;
1932     outCount = OutputToProcess(NoProc, data, length, &error);
1933     if (outCount < length) {
1934         DisplayFatalError(_("Error writing to display"), error, 1);
1935     }
1936 }
1937
1938 void
1939 PackHolding(packed, holding)
1940      char packed[];
1941      char *holding;
1942 {
1943     char *p = holding;
1944     char *q = packed;
1945     int runlength = 0;
1946     int curr = 9999;
1947     do {
1948         if (*p == curr) {
1949             runlength++;
1950         } else {
1951             switch (runlength) {
1952               case 0:
1953                 break;
1954               case 1:
1955                 *q++ = curr;
1956                 break;
1957               case 2:
1958                 *q++ = curr;
1959                 *q++ = curr;
1960                 break;
1961               default:
1962                 sprintf(q, "%d", runlength);
1963                 while (*q) q++;
1964                 *q++ = curr;
1965                 break;
1966             }
1967             runlength = 1;
1968             curr = *p;
1969         }
1970     } while (*p++);
1971     *q = NULLCHAR;
1972 }
1973
1974 /* Telnet protocol requests from the front end */
1975 void
1976 TelnetRequest(ddww, option)
1977      unsigned char ddww, option;
1978 {
1979     unsigned char msg[3];
1980     int outCount, outError;
1981
1982     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1983
1984     if (appData.debugMode) {
1985         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1986         switch (ddww) {
1987           case TN_DO:
1988             ddwwStr = "DO";
1989             break;
1990           case TN_DONT:
1991             ddwwStr = "DONT";
1992             break;
1993           case TN_WILL:
1994             ddwwStr = "WILL";
1995             break;
1996           case TN_WONT:
1997             ddwwStr = "WONT";
1998             break;
1999           default:
2000             ddwwStr = buf1;
2001             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2002             break;
2003         }
2004         switch (option) {
2005           case TN_ECHO:
2006             optionStr = "ECHO";
2007             break;
2008           default:
2009             optionStr = buf2;
2010             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2011             break;
2012         }
2013         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2014     }
2015     msg[0] = TN_IAC;
2016     msg[1] = ddww;
2017     msg[2] = option;
2018     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2019     if (outCount < 3) {
2020         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2021     }
2022 }
2023
2024 void
2025 DoEcho()
2026 {
2027     if (!appData.icsActive) return;
2028     TelnetRequest(TN_DO, TN_ECHO);
2029 }
2030
2031 void
2032 DontEcho()
2033 {
2034     if (!appData.icsActive) return;
2035     TelnetRequest(TN_DONT, TN_ECHO);
2036 }
2037
2038 void
2039 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2040 {
2041     /* put the holdings sent to us by the server on the board holdings area */
2042     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2043     char p;
2044     ChessSquare piece;
2045
2046     if(gameInfo.holdingsWidth < 2)  return;
2047     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2048         return; // prevent overwriting by pre-board holdings
2049
2050     if( (int)lowestPiece >= BlackPawn ) {
2051         holdingsColumn = 0;
2052         countsColumn = 1;
2053         holdingsStartRow = BOARD_HEIGHT-1;
2054         direction = -1;
2055     } else {
2056         holdingsColumn = BOARD_WIDTH-1;
2057         countsColumn = BOARD_WIDTH-2;
2058         holdingsStartRow = 0;
2059         direction = 1;
2060     }
2061
2062     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2063         board[i][holdingsColumn] = EmptySquare;
2064         board[i][countsColumn]   = (ChessSquare) 0;
2065     }
2066     while( (p=*holdings++) != NULLCHAR ) {
2067         piece = CharToPiece( ToUpper(p) );
2068         if(piece == EmptySquare) continue;
2069         /*j = (int) piece - (int) WhitePawn;*/
2070         j = PieceToNumber(piece);
2071         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2072         if(j < 0) continue;               /* should not happen */
2073         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2074         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2075         board[holdingsStartRow+j*direction][countsColumn]++;
2076     }
2077 }
2078
2079
2080 void
2081 VariantSwitch(Board board, VariantClass newVariant)
2082 {
2083    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2084    static Board oldBoard;
2085
2086    startedFromPositionFile = FALSE;
2087    if(gameInfo.variant == newVariant) return;
2088
2089    /* [HGM] This routine is called each time an assignment is made to
2090     * gameInfo.variant during a game, to make sure the board sizes
2091     * are set to match the new variant. If that means adding or deleting
2092     * holdings, we shift the playing board accordingly
2093     * This kludge is needed because in ICS observe mode, we get boards
2094     * of an ongoing game without knowing the variant, and learn about the
2095     * latter only later. This can be because of the move list we requested,
2096     * in which case the game history is refilled from the beginning anyway,
2097     * but also when receiving holdings of a crazyhouse game. In the latter
2098     * case we want to add those holdings to the already received position.
2099     */
2100
2101
2102    if (appData.debugMode) {
2103      fprintf(debugFP, "Switch board from %s to %s\n",
2104              VariantName(gameInfo.variant), VariantName(newVariant));
2105      setbuf(debugFP, NULL);
2106    }
2107    shuffleOpenings = 0;       /* [HGM] shuffle */
2108    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2109    switch(newVariant)
2110      {
2111      case VariantShogi:
2112        newWidth = 9;  newHeight = 9;
2113        gameInfo.holdingsSize = 7;
2114      case VariantBughouse:
2115      case VariantCrazyhouse:
2116        newHoldingsWidth = 2; break;
2117      case VariantGreat:
2118        newWidth = 10;
2119      case VariantSuper:
2120        newHoldingsWidth = 2;
2121        gameInfo.holdingsSize = 8;
2122        break;
2123      case VariantGothic:
2124      case VariantCapablanca:
2125      case VariantCapaRandom:
2126        newWidth = 10;
2127      default:
2128        newHoldingsWidth = gameInfo.holdingsSize = 0;
2129      };
2130
2131    if(newWidth  != gameInfo.boardWidth  ||
2132       newHeight != gameInfo.boardHeight ||
2133       newHoldingsWidth != gameInfo.holdingsWidth ) {
2134
2135      /* shift position to new playing area, if needed */
2136      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2137        for(i=0; i<BOARD_HEIGHT; i++)
2138          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2139            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2140              board[i][j];
2141        for(i=0; i<newHeight; i++) {
2142          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2143          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2144        }
2145      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2146        for(i=0; i<BOARD_HEIGHT; i++)
2147          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2148            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2149              board[i][j];
2150      }
2151      gameInfo.boardWidth  = newWidth;
2152      gameInfo.boardHeight = newHeight;
2153      gameInfo.holdingsWidth = newHoldingsWidth;
2154      gameInfo.variant = newVariant;
2155      InitDrawingSizes(-2, 0);
2156    } else gameInfo.variant = newVariant;
2157    CopyBoard(oldBoard, board);   // remember correctly formatted board
2158      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2159    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2160 }
2161
2162 static int loggedOn = FALSE;
2163
2164 /*-- Game start info cache: --*/
2165 int gs_gamenum;
2166 char gs_kind[MSG_SIZ];
2167 static char player1Name[128] = "";
2168 static char player2Name[128] = "";
2169 static char cont_seq[] = "\n\\   ";
2170 static int player1Rating = -1;
2171 static int player2Rating = -1;
2172 /*----------------------------*/
2173
2174 ColorClass curColor = ColorNormal;
2175 int suppressKibitz = 0;
2176
2177 // [HGM] seekgraph
2178 Boolean soughtPending = FALSE;
2179 Boolean seekGraphUp;
2180 #define MAX_SEEK_ADS 200
2181 #define SQUARE 0x80
2182 char *seekAdList[MAX_SEEK_ADS];
2183 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2184 float tcList[MAX_SEEK_ADS];
2185 char colorList[MAX_SEEK_ADS];
2186 int nrOfSeekAds = 0;
2187 int minRating = 1010, maxRating = 2800;
2188 int hMargin = 10, vMargin = 20, h, w;
2189 extern int squareSize, lineGap;
2190
2191 void
2192 PlotSeekAd(int i)
2193 {
2194         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2195         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2196         if(r < minRating+100 && r >=0 ) r = minRating+100;
2197         if(r > maxRating) r = maxRating;
2198         if(tc < 1.) tc = 1.;
2199         if(tc > 95.) tc = 95.;
2200         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2201         y = ((double)r - minRating)/(maxRating - minRating)
2202             * (h-vMargin-squareSize/8-1) + vMargin;
2203         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2204         if(strstr(seekAdList[i], " u ")) color = 1;
2205         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2206            !strstr(seekAdList[i], "bullet") &&
2207            !strstr(seekAdList[i], "blitz") &&
2208            !strstr(seekAdList[i], "standard") ) color = 2;
2209         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2210         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2211 }
2212
2213 void
2214 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2215 {
2216         char buf[MSG_SIZ], *ext = "";
2217         VariantClass v = StringToVariant(type);
2218         if(strstr(type, "wild")) {
2219             ext = type + 4; // append wild number
2220             if(v == VariantFischeRandom) type = "chess960"; else
2221             if(v == VariantLoadable) type = "setup"; else
2222             type = VariantName(v);
2223         }
2224         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2225         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2226             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2227             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2228             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2229             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2230             seekNrList[nrOfSeekAds] = nr;
2231             zList[nrOfSeekAds] = 0;
2232             seekAdList[nrOfSeekAds++] = StrSave(buf);
2233             if(plot) PlotSeekAd(nrOfSeekAds-1);
2234         }
2235 }
2236
2237 void
2238 EraseSeekDot(int i)
2239 {
2240     int x = xList[i], y = yList[i], d=squareSize/4, k;
2241     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2242     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2243     // now replot every dot that overlapped
2244     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2245         int xx = xList[k], yy = yList[k];
2246         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2247             DrawSeekDot(xx, yy, colorList[k]);
2248     }
2249 }
2250
2251 void
2252 RemoveSeekAd(int nr)
2253 {
2254         int i;
2255         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2256             EraseSeekDot(i);
2257             if(seekAdList[i]) free(seekAdList[i]);
2258             seekAdList[i] = seekAdList[--nrOfSeekAds];
2259             seekNrList[i] = seekNrList[nrOfSeekAds];
2260             ratingList[i] = ratingList[nrOfSeekAds];
2261             colorList[i]  = colorList[nrOfSeekAds];
2262             tcList[i] = tcList[nrOfSeekAds];
2263             xList[i]  = xList[nrOfSeekAds];
2264             yList[i]  = yList[nrOfSeekAds];
2265             zList[i]  = zList[nrOfSeekAds];
2266             seekAdList[nrOfSeekAds] = NULL;
2267             break;
2268         }
2269 }
2270
2271 Boolean
2272 MatchSoughtLine(char *line)
2273 {
2274     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2275     int nr, base, inc, u=0; char dummy;
2276
2277     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2278        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2279        (u=1) &&
2280        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2281         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2282         // match: compact and save the line
2283         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2284         return TRUE;
2285     }
2286     return FALSE;
2287 }
2288
2289 int
2290 DrawSeekGraph()
2291 {
2292     int i;
2293     if(!seekGraphUp) return FALSE;
2294     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2295     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2296
2297     DrawSeekBackground(0, 0, w, h);
2298     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2299     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2300     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2301         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2302         yy = h-1-yy;
2303         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2304         if(i%500 == 0) {
2305             char buf[MSG_SIZ];
2306             snprintf(buf, MSG_SIZ, "%d", i);
2307             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2308         }
2309     }
2310     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2311     for(i=1; i<100; i+=(i<10?1:5)) {
2312         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2313         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2314         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2315             char buf[MSG_SIZ];
2316             snprintf(buf, MSG_SIZ, "%d", i);
2317             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2318         }
2319     }
2320     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2321     return TRUE;
2322 }
2323
2324 int SeekGraphClick(ClickType click, int x, int y, int moving)
2325 {
2326     static int lastDown = 0, displayed = 0, lastSecond;
2327     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2328         if(click == Release || moving) return FALSE;
2329         nrOfSeekAds = 0;
2330         soughtPending = TRUE;
2331         SendToICS(ics_prefix);
2332         SendToICS("sought\n"); // should this be "sought all"?
2333     } else { // issue challenge based on clicked ad
2334         int dist = 10000; int i, closest = 0, second = 0;
2335         for(i=0; i<nrOfSeekAds; i++) {
2336             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2337             if(d < dist) { dist = d; closest = i; }
2338             second += (d - zList[i] < 120); // count in-range ads
2339             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2340         }
2341         if(dist < 120) {
2342             char buf[MSG_SIZ];
2343             second = (second > 1);
2344             if(displayed != closest || second != lastSecond) {
2345                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2346                 lastSecond = second; displayed = closest;
2347             }
2348             if(click == Press) {
2349                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2350                 lastDown = closest;
2351                 return TRUE;
2352             } // on press 'hit', only show info
2353             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2354             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2355             SendToICS(ics_prefix);
2356             SendToICS(buf);
2357             return TRUE; // let incoming board of started game pop down the graph
2358         } else if(click == Release) { // release 'miss' is ignored
2359             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2360             if(moving == 2) { // right up-click
2361                 nrOfSeekAds = 0; // refresh graph
2362                 soughtPending = TRUE;
2363                 SendToICS(ics_prefix);
2364                 SendToICS("sought\n"); // should this be "sought all"?
2365             }
2366             return TRUE;
2367         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2368         // press miss or release hit 'pop down' seek graph
2369         seekGraphUp = FALSE;
2370         DrawPosition(TRUE, NULL);
2371     }
2372     return TRUE;
2373 }
2374
2375 void
2376 read_from_ics(isr, closure, data, count, error)
2377      InputSourceRef isr;
2378      VOIDSTAR closure;
2379      char *data;
2380      int count;
2381      int error;
2382 {
2383 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2384 #define STARTED_NONE 0
2385 #define STARTED_MOVES 1
2386 #define STARTED_BOARD 2
2387 #define STARTED_OBSERVE 3
2388 #define STARTED_HOLDINGS 4
2389 #define STARTED_CHATTER 5
2390 #define STARTED_COMMENT 6
2391 #define STARTED_MOVES_NOHIDE 7
2392
2393     static int started = STARTED_NONE;
2394     static char parse[20000];
2395     static int parse_pos = 0;
2396     static char buf[BUF_SIZE + 1];
2397     static int firstTime = TRUE, intfSet = FALSE;
2398     static ColorClass prevColor = ColorNormal;
2399     static int savingComment = FALSE;
2400     static int cmatch = 0; // continuation sequence match
2401     char *bp;
2402     char str[MSG_SIZ];
2403     int i, oldi;
2404     int buf_len;
2405     int next_out;
2406     int tkind;
2407     int backup;    /* [DM] For zippy color lines */
2408     char *p;
2409     char talker[MSG_SIZ]; // [HGM] chat
2410     int channel;
2411
2412     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2413
2414     if (appData.debugMode) {
2415       if (!error) {
2416         fprintf(debugFP, "<ICS: ");
2417         show_bytes(debugFP, data, count);
2418         fprintf(debugFP, "\n");
2419       }
2420     }
2421
2422     if (appData.debugMode) { int f = forwardMostMove;
2423         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2424                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2425                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2426     }
2427     if (count > 0) {
2428         /* If last read ended with a partial line that we couldn't parse,
2429            prepend it to the new read and try again. */
2430         if (leftover_len > 0) {
2431             for (i=0; i<leftover_len; i++)
2432               buf[i] = buf[leftover_start + i];
2433         }
2434
2435     /* copy new characters into the buffer */
2436     bp = buf + leftover_len;
2437     buf_len=leftover_len;
2438     for (i=0; i<count; i++)
2439     {
2440         // ignore these
2441         if (data[i] == '\r')
2442             continue;
2443
2444         // join lines split by ICS?
2445         if (!appData.noJoin)
2446         {
2447             /*
2448                 Joining just consists of finding matches against the
2449                 continuation sequence, and discarding that sequence
2450                 if found instead of copying it.  So, until a match
2451                 fails, there's nothing to do since it might be the
2452                 complete sequence, and thus, something we don't want
2453                 copied.
2454             */
2455             if (data[i] == cont_seq[cmatch])
2456             {
2457                 cmatch++;
2458                 if (cmatch == strlen(cont_seq))
2459                 {
2460                     cmatch = 0; // complete match.  just reset the counter
2461
2462                     /*
2463                         it's possible for the ICS to not include the space
2464                         at the end of the last word, making our [correct]
2465                         join operation fuse two separate words.  the server
2466                         does this when the space occurs at the width setting.
2467                     */
2468                     if (!buf_len || buf[buf_len-1] != ' ')
2469                     {
2470                         *bp++ = ' ';
2471                         buf_len++;
2472                     }
2473                 }
2474                 continue;
2475             }
2476             else if (cmatch)
2477             {
2478                 /*
2479                     match failed, so we have to copy what matched before
2480                     falling through and copying this character.  In reality,
2481                     this will only ever be just the newline character, but
2482                     it doesn't hurt to be precise.
2483                 */
2484                 strncpy(bp, cont_seq, cmatch);
2485                 bp += cmatch;
2486                 buf_len += cmatch;
2487                 cmatch = 0;
2488             }
2489         }
2490
2491         // copy this char
2492         *bp++ = data[i];
2493         buf_len++;
2494     }
2495
2496         buf[buf_len] = NULLCHAR;
2497 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2498         next_out = 0;
2499         leftover_start = 0;
2500
2501         i = 0;
2502         while (i < buf_len) {
2503             /* Deal with part of the TELNET option negotiation
2504                protocol.  We refuse to do anything beyond the
2505                defaults, except that we allow the WILL ECHO option,
2506                which ICS uses to turn off password echoing when we are
2507                directly connected to it.  We reject this option
2508                if localLineEditing mode is on (always on in xboard)
2509                and we are talking to port 23, which might be a real
2510                telnet server that will try to keep WILL ECHO on permanently.
2511              */
2512             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2513                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2514                 unsigned char option;
2515                 oldi = i;
2516                 switch ((unsigned char) buf[++i]) {
2517                   case TN_WILL:
2518                     if (appData.debugMode)
2519                       fprintf(debugFP, "\n<WILL ");
2520                     switch (option = (unsigned char) buf[++i]) {
2521                       case TN_ECHO:
2522                         if (appData.debugMode)
2523                           fprintf(debugFP, "ECHO ");
2524                         /* Reply only if this is a change, according
2525                            to the protocol rules. */
2526                         if (remoteEchoOption) break;
2527                         if (appData.localLineEditing &&
2528                             atoi(appData.icsPort) == TN_PORT) {
2529                             TelnetRequest(TN_DONT, TN_ECHO);
2530                         } else {
2531                             EchoOff();
2532                             TelnetRequest(TN_DO, TN_ECHO);
2533                             remoteEchoOption = TRUE;
2534                         }
2535                         break;
2536                       default:
2537                         if (appData.debugMode)
2538                           fprintf(debugFP, "%d ", option);
2539                         /* Whatever this is, we don't want it. */
2540                         TelnetRequest(TN_DONT, option);
2541                         break;
2542                     }
2543                     break;
2544                   case TN_WONT:
2545                     if (appData.debugMode)
2546                       fprintf(debugFP, "\n<WONT ");
2547                     switch (option = (unsigned char) buf[++i]) {
2548                       case TN_ECHO:
2549                         if (appData.debugMode)
2550                           fprintf(debugFP, "ECHO ");
2551                         /* Reply only if this is a change, according
2552                            to the protocol rules. */
2553                         if (!remoteEchoOption) break;
2554                         EchoOn();
2555                         TelnetRequest(TN_DONT, TN_ECHO);
2556                         remoteEchoOption = FALSE;
2557                         break;
2558                       default:
2559                         if (appData.debugMode)
2560                           fprintf(debugFP, "%d ", (unsigned char) option);
2561                         /* Whatever this is, it must already be turned
2562                            off, because we never agree to turn on
2563                            anything non-default, so according to the
2564                            protocol rules, we don't reply. */
2565                         break;
2566                     }
2567                     break;
2568                   case TN_DO:
2569                     if (appData.debugMode)
2570                       fprintf(debugFP, "\n<DO ");
2571                     switch (option = (unsigned char) buf[++i]) {
2572                       default:
2573                         /* Whatever this is, we refuse to do it. */
2574                         if (appData.debugMode)
2575                           fprintf(debugFP, "%d ", option);
2576                         TelnetRequest(TN_WONT, option);
2577                         break;
2578                     }
2579                     break;
2580                   case TN_DONT:
2581                     if (appData.debugMode)
2582                       fprintf(debugFP, "\n<DONT ");
2583                     switch (option = (unsigned char) buf[++i]) {
2584                       default:
2585                         if (appData.debugMode)
2586                           fprintf(debugFP, "%d ", option);
2587                         /* Whatever this is, we are already not doing
2588                            it, because we never agree to do anything
2589                            non-default, so according to the protocol
2590                            rules, we don't reply. */
2591                         break;
2592                     }
2593                     break;
2594                   case TN_IAC:
2595                     if (appData.debugMode)
2596                       fprintf(debugFP, "\n<IAC ");
2597                     /* Doubled IAC; pass it through */
2598                     i--;
2599                     break;
2600                   default:
2601                     if (appData.debugMode)
2602                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2603                     /* Drop all other telnet commands on the floor */
2604                     break;
2605                 }
2606                 if (oldi > next_out)
2607                   SendToPlayer(&buf[next_out], oldi - next_out);
2608                 if (++i > next_out)
2609                   next_out = i;
2610                 continue;
2611             }
2612
2613             /* OK, this at least will *usually* work */
2614             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2615                 loggedOn = TRUE;
2616             }
2617
2618             if (loggedOn && !intfSet) {
2619                 if (ics_type == ICS_ICC) {
2620                   snprintf(str, MSG_SIZ,
2621                           "/set-quietly interface %s\n/set-quietly style 12\n",
2622                           programVersion);
2623                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2624                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2625                 } else if (ics_type == ICS_CHESSNET) {
2626                   snprintf(str, MSG_SIZ, "/style 12\n");
2627                 } else {
2628                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2629                   strcat(str, programVersion);
2630                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2631                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2632                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2633 #ifdef WIN32
2634                   strcat(str, "$iset nohighlight 1\n");
2635 #endif
2636                   strcat(str, "$iset lock 1\n$style 12\n");
2637                 }
2638                 SendToICS(str);
2639                 NotifyFrontendLogin();
2640                 intfSet = TRUE;
2641             }
2642
2643             if (started == STARTED_COMMENT) {
2644                 /* Accumulate characters in comment */
2645                 parse[parse_pos++] = buf[i];
2646                 if (buf[i] == '\n') {
2647                     parse[parse_pos] = NULLCHAR;
2648                     if(chattingPartner>=0) {
2649                         char mess[MSG_SIZ];
2650                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2651                         OutputChatMessage(chattingPartner, mess);
2652                         chattingPartner = -1;
2653                         next_out = i+1; // [HGM] suppress printing in ICS window
2654                     } else
2655                     if(!suppressKibitz) // [HGM] kibitz
2656                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2657                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2658                         int nrDigit = 0, nrAlph = 0, j;
2659                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2660                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2661                         parse[parse_pos] = NULLCHAR;
2662                         // try to be smart: if it does not look like search info, it should go to
2663                         // ICS interaction window after all, not to engine-output window.
2664                         for(j=0; j<parse_pos; j++) { // count letters and digits
2665                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2666                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2667                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2668                         }
2669                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2670                             int depth=0; float score;
2671                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2672                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2673                                 pvInfoList[forwardMostMove-1].depth = depth;
2674                                 pvInfoList[forwardMostMove-1].score = 100*score;
2675                             }
2676                             OutputKibitz(suppressKibitz, parse);
2677                         } else {
2678                             char tmp[MSG_SIZ];
2679                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2680                             SendToPlayer(tmp, strlen(tmp));
2681                         }
2682                         next_out = i+1; // [HGM] suppress printing in ICS window
2683                     }
2684                     started = STARTED_NONE;
2685                 } else {
2686                     /* Don't match patterns against characters in comment */
2687                     i++;
2688                     continue;
2689                 }
2690             }
2691             if (started == STARTED_CHATTER) {
2692                 if (buf[i] != '\n') {
2693                     /* Don't match patterns against characters in chatter */
2694                     i++;
2695                     continue;
2696                 }
2697                 started = STARTED_NONE;
2698                 if(suppressKibitz) next_out = i+1;
2699             }
2700
2701             /* Kludge to deal with rcmd protocol */
2702             if (firstTime && looking_at(buf, &i, "\001*")) {
2703                 DisplayFatalError(&buf[1], 0, 1);
2704                 continue;
2705             } else {
2706                 firstTime = FALSE;
2707             }
2708
2709             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2710                 ics_type = ICS_ICC;
2711                 ics_prefix = "/";
2712                 if (appData.debugMode)
2713                   fprintf(debugFP, "ics_type %d\n", ics_type);
2714                 continue;
2715             }
2716             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2717                 ics_type = ICS_FICS;
2718                 ics_prefix = "$";
2719                 if (appData.debugMode)
2720                   fprintf(debugFP, "ics_type %d\n", ics_type);
2721                 continue;
2722             }
2723             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2724                 ics_type = ICS_CHESSNET;
2725                 ics_prefix = "/";
2726                 if (appData.debugMode)
2727                   fprintf(debugFP, "ics_type %d\n", ics_type);
2728                 continue;
2729             }
2730
2731             if (!loggedOn &&
2732                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2733                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2734                  looking_at(buf, &i, "will be \"*\""))) {
2735               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2736               continue;
2737             }
2738
2739             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2740               char buf[MSG_SIZ];
2741               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2742               DisplayIcsInteractionTitle(buf);
2743               have_set_title = TRUE;
2744             }
2745
2746             /* skip finger notes */
2747             if (started == STARTED_NONE &&
2748                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2749                  (buf[i] == '1' && buf[i+1] == '0')) &&
2750                 buf[i+2] == ':' && buf[i+3] == ' ') {
2751               started = STARTED_CHATTER;
2752               i += 3;
2753               continue;
2754             }
2755
2756             oldi = i;
2757             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2758             if(appData.seekGraph) {
2759                 if(soughtPending && MatchSoughtLine(buf+i)) {
2760                     i = strstr(buf+i, "rated") - buf;
2761                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2762                     next_out = leftover_start = i;
2763                     started = STARTED_CHATTER;
2764                     suppressKibitz = TRUE;
2765                     continue;
2766                 }
2767                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2768                         && looking_at(buf, &i, "* ads displayed")) {
2769                     soughtPending = FALSE;
2770                     seekGraphUp = TRUE;
2771                     DrawSeekGraph();
2772                     continue;
2773                 }
2774                 if(appData.autoRefresh) {
2775                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2776                         int s = (ics_type == ICS_ICC); // ICC format differs
2777                         if(seekGraphUp)
2778                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2779                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2780                         looking_at(buf, &i, "*% "); // eat prompt
2781                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2782                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2783                         next_out = i; // suppress
2784                         continue;
2785                     }
2786                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2787                         char *p = star_match[0];
2788                         while(*p) {
2789                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2790                             while(*p && *p++ != ' '); // next
2791                         }
2792                         looking_at(buf, &i, "*% "); // eat prompt
2793                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2794                         next_out = i;
2795                         continue;
2796                     }
2797                 }
2798             }
2799
2800             /* skip formula vars */
2801             if (started == STARTED_NONE &&
2802                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2803               started = STARTED_CHATTER;
2804               i += 3;
2805               continue;
2806             }
2807
2808             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2809             if (appData.autoKibitz && started == STARTED_NONE &&
2810                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2811                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2812                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2813                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2814                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2815                         suppressKibitz = TRUE;
2816                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2817                         next_out = i;
2818                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2819                                 && (gameMode == IcsPlayingWhite)) ||
2820                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2821                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2822                             started = STARTED_CHATTER; // own kibitz we simply discard
2823                         else {
2824                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2825                             parse_pos = 0; parse[0] = NULLCHAR;
2826                             savingComment = TRUE;
2827                             suppressKibitz = gameMode != IcsObserving ? 2 :
2828                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2829                         }
2830                         continue;
2831                 } else
2832                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2833                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2834                          && atoi(star_match[0])) {
2835                     // suppress the acknowledgements of our own autoKibitz
2836                     char *p;
2837                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2838                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2839                     SendToPlayer(star_match[0], strlen(star_match[0]));
2840                     if(looking_at(buf, &i, "*% ")) // eat prompt
2841                         suppressKibitz = FALSE;
2842                     next_out = i;
2843                     continue;
2844                 }
2845             } // [HGM] kibitz: end of patch
2846
2847             // [HGM] chat: intercept tells by users for which we have an open chat window
2848             channel = -1;
2849             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2850                                            looking_at(buf, &i, "* whispers:") ||
2851                                            looking_at(buf, &i, "* kibitzes:") ||
2852                                            looking_at(buf, &i, "* shouts:") ||
2853                                            looking_at(buf, &i, "* c-shouts:") ||
2854                                            looking_at(buf, &i, "--> * ") ||
2855                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2856                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2857                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2858                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2859                 int p;
2860                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2861                 chattingPartner = -1;
2862
2863                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2864                 for(p=0; p<MAX_CHAT; p++) {
2865                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2866                     talker[0] = '['; strcat(talker, "] ");
2867                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2868                     chattingPartner = p; break;
2869                     }
2870                 } else
2871                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2872                 for(p=0; p<MAX_CHAT; p++) {
2873                     if(!strcmp("kibitzes", chatPartner[p])) {
2874                         talker[0] = '['; strcat(talker, "] ");
2875                         chattingPartner = p; break;
2876                     }
2877                 } else
2878                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2879                 for(p=0; p<MAX_CHAT; p++) {
2880                     if(!strcmp("whispers", chatPartner[p])) {
2881                         talker[0] = '['; strcat(talker, "] ");
2882                         chattingPartner = p; break;
2883                     }
2884                 } else
2885                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2886                   if(buf[i-8] == '-' && buf[i-3] == 't')
2887                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2888                     if(!strcmp("c-shouts", chatPartner[p])) {
2889                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2890                         chattingPartner = p; break;
2891                     }
2892                   }
2893                   if(chattingPartner < 0)
2894                   for(p=0; p<MAX_CHAT; p++) {
2895                     if(!strcmp("shouts", chatPartner[p])) {
2896                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2897                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2898                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2899                         chattingPartner = p; break;
2900                     }
2901                   }
2902                 }
2903                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2904                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2905                     talker[0] = 0; Colorize(ColorTell, FALSE);
2906                     chattingPartner = p; break;
2907                 }
2908                 if(chattingPartner<0) i = oldi; else {
2909                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2910                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2911                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2912                     started = STARTED_COMMENT;
2913                     parse_pos = 0; parse[0] = NULLCHAR;
2914                     savingComment = 3 + chattingPartner; // counts as TRUE
2915                     suppressKibitz = TRUE;
2916                     continue;
2917                 }
2918             } // [HGM] chat: end of patch
2919
2920             if (appData.zippyTalk || appData.zippyPlay) {
2921                 /* [DM] Backup address for color zippy lines */
2922                 backup = i;
2923 #if ZIPPY
2924                if (loggedOn == TRUE)
2925                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2926                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2927 #endif
2928             } // [DM] 'else { ' deleted
2929                 if (
2930                     /* Regular tells and says */
2931                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2932                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2933                     looking_at(buf, &i, "* says: ") ||
2934                     /* Don't color "message" or "messages" output */
2935                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2936                     looking_at(buf, &i, "*. * at *:*: ") ||
2937                     looking_at(buf, &i, "--* (*:*): ") ||
2938                     /* Message notifications (same color as tells) */
2939                     looking_at(buf, &i, "* has left a message ") ||
2940                     looking_at(buf, &i, "* just sent you a message:\n") ||
2941                     /* Whispers and kibitzes */
2942                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2943                     looking_at(buf, &i, "* kibitzes: ") ||
2944                     /* Channel tells */
2945                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2946
2947                   if (tkind == 1 && strchr(star_match[0], ':')) {
2948                       /* Avoid "tells you:" spoofs in channels */
2949                      tkind = 3;
2950                   }
2951                   if (star_match[0][0] == NULLCHAR ||
2952                       strchr(star_match[0], ' ') ||
2953                       (tkind == 3 && strchr(star_match[1], ' '))) {
2954                     /* Reject bogus matches */
2955                     i = oldi;
2956                   } else {
2957                     if (appData.colorize) {
2958                       if (oldi > next_out) {
2959                         SendToPlayer(&buf[next_out], oldi - next_out);
2960                         next_out = oldi;
2961                       }
2962                       switch (tkind) {
2963                       case 1:
2964                         Colorize(ColorTell, FALSE);
2965                         curColor = ColorTell;
2966                         break;
2967                       case 2:
2968                         Colorize(ColorKibitz, FALSE);
2969                         curColor = ColorKibitz;
2970                         break;
2971                       case 3:
2972                         p = strrchr(star_match[1], '(');
2973                         if (p == NULL) {
2974                           p = star_match[1];
2975                         } else {
2976                           p++;
2977                         }
2978                         if (atoi(p) == 1) {
2979                           Colorize(ColorChannel1, FALSE);
2980                           curColor = ColorChannel1;
2981                         } else {
2982                           Colorize(ColorChannel, FALSE);
2983                           curColor = ColorChannel;
2984                         }
2985                         break;
2986                       case 5:
2987                         curColor = ColorNormal;
2988                         break;
2989                       }
2990                     }
2991                     if (started == STARTED_NONE && appData.autoComment &&
2992                         (gameMode == IcsObserving ||
2993                          gameMode == IcsPlayingWhite ||
2994                          gameMode == IcsPlayingBlack)) {
2995                       parse_pos = i - oldi;
2996                       memcpy(parse, &buf[oldi], parse_pos);
2997                       parse[parse_pos] = NULLCHAR;
2998                       started = STARTED_COMMENT;
2999                       savingComment = TRUE;
3000                     } else {
3001                       started = STARTED_CHATTER;
3002                       savingComment = FALSE;
3003                     }
3004                     loggedOn = TRUE;
3005                     continue;
3006                   }
3007                 }
3008
3009                 if (looking_at(buf, &i, "* s-shouts: ") ||
3010                     looking_at(buf, &i, "* c-shouts: ")) {
3011                     if (appData.colorize) {
3012                         if (oldi > next_out) {
3013                             SendToPlayer(&buf[next_out], oldi - next_out);
3014                             next_out = oldi;
3015                         }
3016                         Colorize(ColorSShout, FALSE);
3017                         curColor = ColorSShout;
3018                     }
3019                     loggedOn = TRUE;
3020                     started = STARTED_CHATTER;
3021                     continue;
3022                 }
3023
3024                 if (looking_at(buf, &i, "--->")) {
3025                     loggedOn = TRUE;
3026                     continue;
3027                 }
3028
3029                 if (looking_at(buf, &i, "* shouts: ") ||
3030                     looking_at(buf, &i, "--> ")) {
3031                     if (appData.colorize) {
3032                         if (oldi > next_out) {
3033                             SendToPlayer(&buf[next_out], oldi - next_out);
3034                             next_out = oldi;
3035                         }
3036                         Colorize(ColorShout, FALSE);
3037                         curColor = ColorShout;
3038                     }
3039                     loggedOn = TRUE;
3040                     started = STARTED_CHATTER;
3041                     continue;
3042                 }
3043
3044                 if (looking_at( buf, &i, "Challenge:")) {
3045                     if (appData.colorize) {
3046                         if (oldi > next_out) {
3047                             SendToPlayer(&buf[next_out], oldi - next_out);
3048                             next_out = oldi;
3049                         }
3050                         Colorize(ColorChallenge, FALSE);
3051                         curColor = ColorChallenge;
3052                     }
3053                     loggedOn = TRUE;
3054                     continue;
3055                 }
3056
3057                 if (looking_at(buf, &i, "* offers you") ||
3058                     looking_at(buf, &i, "* offers to be") ||
3059                     looking_at(buf, &i, "* would like to") ||
3060                     looking_at(buf, &i, "* requests to") ||
3061                     looking_at(buf, &i, "Your opponent offers") ||
3062                     looking_at(buf, &i, "Your opponent requests")) {
3063
3064                     if (appData.colorize) {
3065                         if (oldi > next_out) {
3066                             SendToPlayer(&buf[next_out], oldi - next_out);
3067                             next_out = oldi;
3068                         }
3069                         Colorize(ColorRequest, FALSE);
3070                         curColor = ColorRequest;
3071                     }
3072                     continue;
3073                 }
3074
3075                 if (looking_at(buf, &i, "* (*) seeking")) {
3076                     if (appData.colorize) {
3077                         if (oldi > next_out) {
3078                             SendToPlayer(&buf[next_out], oldi - next_out);
3079                             next_out = oldi;
3080                         }
3081                         Colorize(ColorSeek, FALSE);
3082                         curColor = ColorSeek;
3083                     }
3084                     continue;
3085             }
3086
3087             if (looking_at(buf, &i, "\\   ")) {
3088                 if (prevColor != ColorNormal) {
3089                     if (oldi > next_out) {
3090                         SendToPlayer(&buf[next_out], oldi - next_out);
3091                         next_out = oldi;
3092                     }
3093                     Colorize(prevColor, TRUE);
3094                     curColor = prevColor;
3095                 }
3096                 if (savingComment) {
3097                     parse_pos = i - oldi;
3098                     memcpy(parse, &buf[oldi], parse_pos);
3099                     parse[parse_pos] = NULLCHAR;
3100                     started = STARTED_COMMENT;
3101                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3102                         chattingPartner = savingComment - 3; // kludge to remember the box
3103                 } else {
3104                     started = STARTED_CHATTER;
3105                 }
3106                 continue;
3107             }
3108
3109             if (looking_at(buf, &i, "Black Strength :") ||
3110                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3111                 looking_at(buf, &i, "<10>") ||
3112                 looking_at(buf, &i, "#@#")) {
3113                 /* Wrong board style */
3114                 loggedOn = TRUE;
3115                 SendToICS(ics_prefix);
3116                 SendToICS("set style 12\n");
3117                 SendToICS(ics_prefix);
3118                 SendToICS("refresh\n");
3119                 continue;
3120             }
3121
3122             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3123                 ICSInitScript();
3124                 have_sent_ICS_logon = 1;
3125                 continue;
3126             }
3127
3128             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3129                 (looking_at(buf, &i, "\n<12> ") ||
3130                  looking_at(buf, &i, "<12> "))) {
3131                 loggedOn = TRUE;
3132                 if (oldi > next_out) {
3133                     SendToPlayer(&buf[next_out], oldi - next_out);
3134                 }
3135                 next_out = i;
3136                 started = STARTED_BOARD;
3137                 parse_pos = 0;
3138                 continue;
3139             }
3140
3141             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3142                 looking_at(buf, &i, "<b1> ")) {
3143                 if (oldi > next_out) {
3144                     SendToPlayer(&buf[next_out], oldi - next_out);
3145                 }
3146                 next_out = i;
3147                 started = STARTED_HOLDINGS;
3148                 parse_pos = 0;
3149                 continue;
3150             }
3151
3152             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3153                 loggedOn = TRUE;
3154                 /* Header for a move list -- first line */
3155
3156                 switch (ics_getting_history) {
3157                   case H_FALSE:
3158                     switch (gameMode) {
3159                       case IcsIdle:
3160                       case BeginningOfGame:
3161                         /* User typed "moves" or "oldmoves" while we
3162                            were idle.  Pretend we asked for these
3163                            moves and soak them up so user can step
3164                            through them and/or save them.
3165                            */
3166                         Reset(FALSE, TRUE);
3167                         gameMode = IcsObserving;
3168                         ModeHighlight();
3169                         ics_gamenum = -1;
3170                         ics_getting_history = H_GOT_UNREQ_HEADER;
3171                         break;
3172                       case EditGame: /*?*/
3173                       case EditPosition: /*?*/
3174                         /* Should above feature work in these modes too? */
3175                         /* For now it doesn't */
3176                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3177                         break;
3178                       default:
3179                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3180                         break;
3181                     }
3182                     break;
3183                   case H_REQUESTED:
3184                     /* Is this the right one? */
3185                     if (gameInfo.white && gameInfo.black &&
3186                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3187                         strcmp(gameInfo.black, star_match[2]) == 0) {
3188                         /* All is well */
3189                         ics_getting_history = H_GOT_REQ_HEADER;
3190                     }
3191                     break;
3192                   case H_GOT_REQ_HEADER:
3193                   case H_GOT_UNREQ_HEADER:
3194                   case H_GOT_UNWANTED_HEADER:
3195                   case H_GETTING_MOVES:
3196                     /* Should not happen */
3197                     DisplayError(_("Error gathering move list: two headers"), 0);
3198                     ics_getting_history = H_FALSE;
3199                     break;
3200                 }
3201
3202                 /* Save player ratings into gameInfo if needed */
3203                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3204                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3205                     (gameInfo.whiteRating == -1 ||
3206                      gameInfo.blackRating == -1)) {
3207
3208                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3209                     gameInfo.blackRating = string_to_rating(star_match[3]);
3210                     if (appData.debugMode)
3211                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3212                               gameInfo.whiteRating, gameInfo.blackRating);
3213                 }
3214                 continue;
3215             }
3216
3217             if (looking_at(buf, &i,
3218               "* * match, initial time: * minute*, increment: * second")) {
3219                 /* Header for a move list -- second line */
3220                 /* Initial board will follow if this is a wild game */
3221                 if (gameInfo.event != NULL) free(gameInfo.event);
3222                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3223                 gameInfo.event = StrSave(str);
3224                 /* [HGM] we switched variant. Translate boards if needed. */
3225                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3226                 continue;
3227             }
3228
3229             if (looking_at(buf, &i, "Move  ")) {
3230                 /* Beginning of a move list */
3231                 switch (ics_getting_history) {
3232                   case H_FALSE:
3233                     /* Normally should not happen */
3234                     /* Maybe user hit reset while we were parsing */
3235                     break;
3236                   case H_REQUESTED:
3237                     /* Happens if we are ignoring a move list that is not
3238                      * the one we just requested.  Common if the user
3239                      * tries to observe two games without turning off
3240                      * getMoveList */
3241                     break;
3242                   case H_GETTING_MOVES:
3243                     /* Should not happen */
3244                     DisplayError(_("Error gathering move list: nested"), 0);
3245                     ics_getting_history = H_FALSE;
3246                     break;
3247                   case H_GOT_REQ_HEADER:
3248                     ics_getting_history = H_GETTING_MOVES;
3249                     started = STARTED_MOVES;
3250                     parse_pos = 0;
3251                     if (oldi > next_out) {
3252                         SendToPlayer(&buf[next_out], oldi - next_out);
3253                     }
3254                     break;
3255                   case H_GOT_UNREQ_HEADER:
3256                     ics_getting_history = H_GETTING_MOVES;
3257                     started = STARTED_MOVES_NOHIDE;
3258                     parse_pos = 0;
3259                     break;
3260                   case H_GOT_UNWANTED_HEADER:
3261                     ics_getting_history = H_FALSE;
3262                     break;
3263                 }
3264                 continue;
3265             }
3266
3267             if (looking_at(buf, &i, "% ") ||
3268                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3269                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3270                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3271                     soughtPending = FALSE;
3272                     seekGraphUp = TRUE;
3273                     DrawSeekGraph();
3274                 }
3275                 if(suppressKibitz) next_out = i;
3276                 savingComment = FALSE;
3277                 suppressKibitz = 0;
3278                 switch (started) {
3279                   case STARTED_MOVES:
3280                   case STARTED_MOVES_NOHIDE:
3281                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3282                     parse[parse_pos + i - oldi] = NULLCHAR;
3283                     ParseGameHistory(parse);
3284 #if ZIPPY
3285                     if (appData.zippyPlay && first.initDone) {
3286                         FeedMovesToProgram(&first, forwardMostMove);
3287                         if (gameMode == IcsPlayingWhite) {
3288                             if (WhiteOnMove(forwardMostMove)) {
3289                                 if (first.sendTime) {
3290                                   if (first.useColors) {
3291                                     SendToProgram("black\n", &first);
3292                                   }
3293                                   SendTimeRemaining(&first, TRUE);
3294                                 }
3295                                 if (first.useColors) {
3296                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3297                                 }
3298                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3299                                 first.maybeThinking = TRUE;
3300                             } else {
3301                                 if (first.usePlayother) {
3302                                   if (first.sendTime) {
3303                                     SendTimeRemaining(&first, TRUE);
3304                                   }
3305                                   SendToProgram("playother\n", &first);
3306                                   firstMove = FALSE;
3307                                 } else {
3308                                   firstMove = TRUE;
3309                                 }
3310                             }
3311                         } else if (gameMode == IcsPlayingBlack) {
3312                             if (!WhiteOnMove(forwardMostMove)) {
3313                                 if (first.sendTime) {
3314                                   if (first.useColors) {
3315                                     SendToProgram("white\n", &first);
3316                                   }
3317                                   SendTimeRemaining(&first, FALSE);
3318                                 }
3319                                 if (first.useColors) {
3320                                   SendToProgram("black\n", &first);
3321                                 }
3322                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3323                                 first.maybeThinking = TRUE;
3324                             } else {
3325                                 if (first.usePlayother) {
3326                                   if (first.sendTime) {
3327                                     SendTimeRemaining(&first, FALSE);
3328                                   }
3329                                   SendToProgram("playother\n", &first);
3330                                   firstMove = FALSE;
3331                                 } else {
3332                                   firstMove = TRUE;
3333                                 }
3334                             }
3335                         }
3336                     }
3337 #endif
3338                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3339                         /* Moves came from oldmoves or moves command
3340                            while we weren't doing anything else.
3341                            */
3342                         currentMove = forwardMostMove;
3343                         ClearHighlights();/*!!could figure this out*/
3344                         flipView = appData.flipView;
3345                         DrawPosition(TRUE, boards[currentMove]);
3346                         DisplayBothClocks();
3347                         snprintf(str, MSG_SIZ, "%s vs. %s",
3348                                 gameInfo.white, gameInfo.black);
3349                         DisplayTitle(str);
3350                         gameMode = IcsIdle;
3351                     } else {
3352                         /* Moves were history of an active game */
3353                         if (gameInfo.resultDetails != NULL) {
3354                             free(gameInfo.resultDetails);
3355                             gameInfo.resultDetails = NULL;
3356                         }
3357                     }
3358                     HistorySet(parseList, backwardMostMove,
3359                                forwardMostMove, currentMove-1);
3360                     DisplayMove(currentMove - 1);
3361                     if (started == STARTED_MOVES) next_out = i;
3362                     started = STARTED_NONE;
3363                     ics_getting_history = H_FALSE;
3364                     break;
3365
3366                   case STARTED_OBSERVE:
3367                     started = STARTED_NONE;
3368                     SendToICS(ics_prefix);
3369                     SendToICS("refresh\n");
3370                     break;
3371
3372                   default:
3373                     break;
3374                 }
3375                 if(bookHit) { // [HGM] book: simulate book reply
3376                     static char bookMove[MSG_SIZ]; // a bit generous?
3377
3378                     programStats.nodes = programStats.depth = programStats.time =
3379                     programStats.score = programStats.got_only_move = 0;
3380                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3381
3382                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3383                     strcat(bookMove, bookHit);
3384                     HandleMachineMove(bookMove, &first);
3385                 }
3386                 continue;
3387             }
3388
3389             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3390                  started == STARTED_HOLDINGS ||
3391                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3392                 /* Accumulate characters in move list or board */
3393                 parse[parse_pos++] = buf[i];
3394             }
3395
3396             /* Start of game messages.  Mostly we detect start of game
3397                when the first board image arrives.  On some versions
3398                of the ICS, though, we need to do a "refresh" after starting
3399                to observe in order to get the current board right away. */
3400             if (looking_at(buf, &i, "Adding game * to observation list")) {
3401                 started = STARTED_OBSERVE;
3402                 continue;
3403             }
3404
3405             /* Handle auto-observe */
3406             if (appData.autoObserve &&
3407                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3408                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3409                 char *player;
3410                 /* Choose the player that was highlighted, if any. */
3411                 if (star_match[0][0] == '\033' ||
3412                     star_match[1][0] != '\033') {
3413                     player = star_match[0];
3414                 } else {
3415                     player = star_match[2];
3416                 }
3417                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3418                         ics_prefix, StripHighlightAndTitle(player));
3419                 SendToICS(str);
3420
3421                 /* Save ratings from notify string */
3422                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3423                 player1Rating = string_to_rating(star_match[1]);
3424                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3425                 player2Rating = string_to_rating(star_match[3]);
3426
3427                 if (appData.debugMode)
3428                   fprintf(debugFP,
3429                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3430                           player1Name, player1Rating,
3431                           player2Name, player2Rating);
3432
3433                 continue;
3434             }
3435
3436             /* Deal with automatic examine mode after a game,
3437                and with IcsObserving -> IcsExamining transition */
3438             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3439                 looking_at(buf, &i, "has made you an examiner of game *")) {
3440
3441                 int gamenum = atoi(star_match[0]);
3442                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3443                     gamenum == ics_gamenum) {
3444                     /* We were already playing or observing this game;
3445                        no need to refetch history */
3446                     gameMode = IcsExamining;
3447                     if (pausing) {
3448                         pauseExamForwardMostMove = forwardMostMove;
3449                     } else if (currentMove < forwardMostMove) {
3450                         ForwardInner(forwardMostMove);
3451                     }
3452                 } else {
3453                     /* I don't think this case really can happen */
3454                     SendToICS(ics_prefix);
3455                     SendToICS("refresh\n");
3456                 }
3457                 continue;
3458             }
3459
3460             /* Error messages */
3461 //          if (ics_user_moved) {
3462             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3463                 if (looking_at(buf, &i, "Illegal move") ||
3464                     looking_at(buf, &i, "Not a legal move") ||
3465                     looking_at(buf, &i, "Your king is in check") ||
3466                     looking_at(buf, &i, "It isn't your turn") ||
3467                     looking_at(buf, &i, "It is not your move")) {
3468                     /* Illegal move */
3469                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3470                         currentMove = forwardMostMove-1;
3471                         DisplayMove(currentMove - 1); /* before DMError */
3472                         DrawPosition(FALSE, boards[currentMove]);
3473                         SwitchClocks(forwardMostMove-1); // [HGM] race
3474                         DisplayBothClocks();
3475                     }
3476                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3477                     ics_user_moved = 0;
3478                     continue;
3479                 }
3480             }
3481
3482             if (looking_at(buf, &i, "still have time") ||
3483                 looking_at(buf, &i, "not out of time") ||
3484                 looking_at(buf, &i, "either player is out of time") ||
3485                 looking_at(buf, &i, "has timeseal; checking")) {
3486                 /* We must have called his flag a little too soon */
3487                 whiteFlag = blackFlag = FALSE;
3488                 continue;
3489             }
3490
3491             if (looking_at(buf, &i, "added * seconds to") ||
3492                 looking_at(buf, &i, "seconds were added to")) {
3493                 /* Update the clocks */
3494                 SendToICS(ics_prefix);
3495                 SendToICS("refresh\n");
3496                 continue;
3497             }
3498
3499             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3500                 ics_clock_paused = TRUE;
3501                 StopClocks();
3502                 continue;
3503             }
3504
3505             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3506                 ics_clock_paused = FALSE;
3507                 StartClocks();
3508                 continue;
3509             }
3510
3511             /* Grab player ratings from the Creating: message.
3512                Note we have to check for the special case when
3513                the ICS inserts things like [white] or [black]. */
3514             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3515                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3516                 /* star_matches:
3517                    0    player 1 name (not necessarily white)
3518                    1    player 1 rating
3519                    2    empty, white, or black (IGNORED)
3520                    3    player 2 name (not necessarily black)
3521                    4    player 2 rating
3522
3523                    The names/ratings are sorted out when the game
3524                    actually starts (below).
3525                 */
3526                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3527                 player1Rating = string_to_rating(star_match[1]);
3528                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3529                 player2Rating = string_to_rating(star_match[4]);
3530
3531                 if (appData.debugMode)
3532                   fprintf(debugFP,
3533                           "Ratings from 'Creating:' %s %d, %s %d\n",
3534                           player1Name, player1Rating,
3535                           player2Name, player2Rating);
3536
3537                 continue;
3538             }
3539
3540             /* Improved generic start/end-of-game messages */
3541             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3542                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3543                 /* If tkind == 0: */
3544                 /* star_match[0] is the game number */
3545                 /*           [1] is the white player's name */
3546                 /*           [2] is the black player's name */
3547                 /* For end-of-game: */
3548                 /*           [3] is the reason for the game end */
3549                 /*           [4] is a PGN end game-token, preceded by " " */
3550                 /* For start-of-game: */
3551                 /*           [3] begins with "Creating" or "Continuing" */
3552                 /*           [4] is " *" or empty (don't care). */
3553                 int gamenum = atoi(star_match[0]);
3554                 char *whitename, *blackname, *why, *endtoken;
3555                 ChessMove endtype = EndOfFile;
3556
3557                 if (tkind == 0) {
3558                   whitename = star_match[1];
3559                   blackname = star_match[2];
3560                   why = star_match[3];
3561                   endtoken = star_match[4];
3562                 } else {
3563                   whitename = star_match[1];
3564                   blackname = star_match[3];
3565                   why = star_match[5];
3566                   endtoken = star_match[6];
3567                 }
3568
3569                 /* Game start messages */
3570                 if (strncmp(why, "Creating ", 9) == 0 ||
3571                     strncmp(why, "Continuing ", 11) == 0) {
3572                     gs_gamenum = gamenum;
3573                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3574                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3575 #if ZIPPY
3576                     if (appData.zippyPlay) {
3577                         ZippyGameStart(whitename, blackname);
3578                     }
3579 #endif /*ZIPPY*/
3580                     partnerBoardValid = FALSE; // [HGM] bughouse
3581                     continue;
3582                 }
3583
3584                 /* Game end messages */
3585                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3586                     ics_gamenum != gamenum) {
3587                     continue;
3588                 }
3589                 while (endtoken[0] == ' ') endtoken++;
3590                 switch (endtoken[0]) {
3591                   case '*':
3592                   default:
3593                     endtype = GameUnfinished;
3594                     break;
3595                   case '0':
3596                     endtype = BlackWins;
3597                     break;
3598                   case '1':
3599                     if (endtoken[1] == '/')
3600                       endtype = GameIsDrawn;
3601                     else
3602                       endtype = WhiteWins;
3603                     break;
3604                 }
3605                 GameEnds(endtype, why, GE_ICS);
3606 #if ZIPPY
3607                 if (appData.zippyPlay && first.initDone) {
3608                     ZippyGameEnd(endtype, why);
3609                     if (first.pr == NULL) {
3610                       /* Start the next process early so that we'll
3611                          be ready for the next challenge */
3612                       StartChessProgram(&first);
3613                     }
3614                     /* Send "new" early, in case this command takes
3615                        a long time to finish, so that we'll be ready
3616                        for the next challenge. */
3617                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3618                     Reset(TRUE, TRUE);
3619                 }
3620 #endif /*ZIPPY*/
3621                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3622                 continue;
3623             }
3624
3625             if (looking_at(buf, &i, "Removing game * from observation") ||
3626                 looking_at(buf, &i, "no longer observing game *") ||
3627                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3628                 if (gameMode == IcsObserving &&
3629                     atoi(star_match[0]) == ics_gamenum)
3630                   {
3631                       /* icsEngineAnalyze */
3632                       if (appData.icsEngineAnalyze) {
3633                             ExitAnalyzeMode();
3634                             ModeHighlight();
3635                       }
3636                       StopClocks();
3637                       gameMode = IcsIdle;
3638                       ics_gamenum = -1;
3639                       ics_user_moved = FALSE;
3640                   }
3641                 continue;
3642             }
3643
3644             if (looking_at(buf, &i, "no longer examining game *")) {
3645                 if (gameMode == IcsExamining &&
3646                     atoi(star_match[0]) == ics_gamenum)
3647                   {
3648                       gameMode = IcsIdle;
3649                       ics_gamenum = -1;
3650                       ics_user_moved = FALSE;
3651                   }
3652                 continue;
3653             }
3654
3655             /* Advance leftover_start past any newlines we find,
3656                so only partial lines can get reparsed */
3657             if (looking_at(buf, &i, "\n")) {
3658                 prevColor = curColor;
3659                 if (curColor != ColorNormal) {
3660                     if (oldi > next_out) {
3661                         SendToPlayer(&buf[next_out], oldi - next_out);
3662                         next_out = oldi;
3663                     }
3664                     Colorize(ColorNormal, FALSE);
3665                     curColor = ColorNormal;
3666                 }
3667                 if (started == STARTED_BOARD) {
3668                     started = STARTED_NONE;
3669                     parse[parse_pos] = NULLCHAR;
3670                     ParseBoard12(parse);
3671                     ics_user_moved = 0;
3672
3673                     /* Send premove here */
3674                     if (appData.premove) {
3675                       char str[MSG_SIZ];
3676                       if (currentMove == 0 &&
3677                           gameMode == IcsPlayingWhite &&
3678                           appData.premoveWhite) {
3679                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3680                         if (appData.debugMode)
3681                           fprintf(debugFP, "Sending premove:\n");
3682                         SendToICS(str);
3683                       } else if (currentMove == 1 &&
3684                                  gameMode == IcsPlayingBlack &&
3685                                  appData.premoveBlack) {
3686                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3687                         if (appData.debugMode)
3688                           fprintf(debugFP, "Sending premove:\n");
3689                         SendToICS(str);
3690                       } else if (gotPremove) {
3691                         gotPremove = 0;
3692                         ClearPremoveHighlights();
3693                         if (appData.debugMode)
3694                           fprintf(debugFP, "Sending premove:\n");
3695                           UserMoveEvent(premoveFromX, premoveFromY,
3696                                         premoveToX, premoveToY,
3697                                         premovePromoChar);
3698                       }
3699                     }
3700
3701                     /* Usually suppress following prompt */
3702                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3703                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3704                         if (looking_at(buf, &i, "*% ")) {
3705                             savingComment = FALSE;
3706                             suppressKibitz = 0;
3707                         }
3708                     }
3709                     next_out = i;
3710                 } else if (started == STARTED_HOLDINGS) {
3711                     int gamenum;
3712                     char new_piece[MSG_SIZ];
3713                     started = STARTED_NONE;
3714                     parse[parse_pos] = NULLCHAR;
3715                     if (appData.debugMode)
3716                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3717                                                         parse, currentMove);
3718                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3719                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3720                         if (gameInfo.variant == VariantNormal) {
3721                           /* [HGM] We seem to switch variant during a game!
3722                            * Presumably no holdings were displayed, so we have
3723                            * to move the position two files to the right to
3724                            * create room for them!
3725                            */
3726                           VariantClass newVariant;
3727                           switch(gameInfo.boardWidth) { // base guess on board width
3728                                 case 9:  newVariant = VariantShogi; break;
3729                                 case 10: newVariant = VariantGreat; break;
3730                                 default: newVariant = VariantCrazyhouse; break;
3731                           }
3732                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3733                           /* Get a move list just to see the header, which
3734                              will tell us whether this is really bug or zh */
3735                           if (ics_getting_history == H_FALSE) {
3736                             ics_getting_history = H_REQUESTED;
3737                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3738                             SendToICS(str);
3739                           }
3740                         }
3741                         new_piece[0] = NULLCHAR;
3742                         sscanf(parse, "game %d white [%s black [%s <- %s",
3743                                &gamenum, white_holding, black_holding,
3744                                new_piece);
3745                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3746                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3747                         /* [HGM] copy holdings to board holdings area */
3748                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3749                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3750                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3751 #if ZIPPY
3752                         if (appData.zippyPlay && first.initDone) {
3753                             ZippyHoldings(white_holding, black_holding,
3754                                           new_piece);
3755                         }
3756 #endif /*ZIPPY*/
3757                         if (tinyLayout || smallLayout) {
3758                             char wh[16], bh[16];
3759                             PackHolding(wh, white_holding);
3760                             PackHolding(bh, black_holding);
3761                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3762                                     gameInfo.white, gameInfo.black);
3763                         } else {
3764                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3765                                     gameInfo.white, white_holding,
3766                                     gameInfo.black, black_holding);
3767                         }
3768                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3769                         DrawPosition(FALSE, boards[currentMove]);
3770                         DisplayTitle(str);
3771                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3772                         sscanf(parse, "game %d white [%s black [%s <- %s",
3773                                &gamenum, white_holding, black_holding,
3774                                new_piece);
3775                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3776                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3777                         /* [HGM] copy holdings to partner-board holdings area */
3778                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3779                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3780                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3781                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3782                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3783                       }
3784                     }
3785                     /* Suppress following prompt */
3786                     if (looking_at(buf, &i, "*% ")) {
3787                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3788                         savingComment = FALSE;
3789                         suppressKibitz = 0;
3790                     }
3791                     next_out = i;
3792                 }
3793                 continue;
3794             }
3795
3796             i++;                /* skip unparsed character and loop back */
3797         }
3798
3799         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3800 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3801 //          SendToPlayer(&buf[next_out], i - next_out);
3802             started != STARTED_HOLDINGS && leftover_start > next_out) {
3803             SendToPlayer(&buf[next_out], leftover_start - next_out);
3804             next_out = i;
3805         }
3806
3807         leftover_len = buf_len - leftover_start;
3808         /* if buffer ends with something we couldn't parse,
3809            reparse it after appending the next read */
3810
3811     } else if (count == 0) {
3812         RemoveInputSource(isr);
3813         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3814     } else {
3815         DisplayFatalError(_("Error reading from ICS"), error, 1);
3816     }
3817 }
3818
3819
3820 /* Board style 12 looks like this:
3821
3822    <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
3823
3824  * The "<12> " is stripped before it gets to this routine.  The two
3825  * trailing 0's (flip state and clock ticking) are later addition, and
3826  * some chess servers may not have them, or may have only the first.
3827  * Additional trailing fields may be added in the future.
3828  */
3829
3830 #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"
3831
3832 #define RELATION_OBSERVING_PLAYED    0
3833 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3834 #define RELATION_PLAYING_MYMOVE      1
3835 #define RELATION_PLAYING_NOTMYMOVE  -1
3836 #define RELATION_EXAMINING           2
3837 #define RELATION_ISOLATED_BOARD     -3
3838 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3839
3840 void
3841 ParseBoard12(string)
3842      char *string;
3843 {
3844     GameMode newGameMode;
3845     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3846     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3847     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3848     char to_play, board_chars[200];
3849     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3850     char black[32], white[32];
3851     Board board;
3852     int prevMove = currentMove;
3853     int ticking = 2;
3854     ChessMove moveType;
3855     int fromX, fromY, toX, toY;
3856     char promoChar;
3857     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3858     char *bookHit = NULL; // [HGM] book
3859     Boolean weird = FALSE, reqFlag = FALSE;
3860
3861     fromX = fromY = toX = toY = -1;
3862
3863     newGame = FALSE;
3864
3865     if (appData.debugMode)
3866       fprintf(debugFP, _("Parsing board: %s\n"), string);
3867
3868     move_str[0] = NULLCHAR;
3869     elapsed_time[0] = NULLCHAR;
3870     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3871         int  i = 0, j;
3872         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3873             if(string[i] == ' ') { ranks++; files = 0; }
3874             else files++;
3875             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3876             i++;
3877         }
3878         for(j = 0; j <i; j++) board_chars[j] = string[j];
3879         board_chars[i] = '\0';
3880         string += i + 1;
3881     }
3882     n = sscanf(string, PATTERN, &to_play, &double_push,
3883                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3884                &gamenum, white, black, &relation, &basetime, &increment,
3885                &white_stren, &black_stren, &white_time, &black_time,
3886                &moveNum, str, elapsed_time, move_str, &ics_flip,
3887                &ticking);
3888
3889     if (n < 21) {
3890         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3891         DisplayError(str, 0);
3892         return;
3893     }
3894
3895     /* Convert the move number to internal form */
3896     moveNum = (moveNum - 1) * 2;
3897     if (to_play == 'B') moveNum++;
3898     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3899       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3900                         0, 1);
3901       return;
3902     }
3903
3904     switch (relation) {
3905       case RELATION_OBSERVING_PLAYED:
3906       case RELATION_OBSERVING_STATIC:
3907         if (gamenum == -1) {
3908             /* Old ICC buglet */
3909             relation = RELATION_OBSERVING_STATIC;
3910         }
3911         newGameMode = IcsObserving;
3912         break;
3913       case RELATION_PLAYING_MYMOVE:
3914       case RELATION_PLAYING_NOTMYMOVE:
3915         newGameMode =
3916           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3917             IcsPlayingWhite : IcsPlayingBlack;
3918         break;
3919       case RELATION_EXAMINING:
3920         newGameMode = IcsExamining;
3921         break;
3922       case RELATION_ISOLATED_BOARD:
3923       default:
3924         /* Just display this board.  If user was doing something else,
3925            we will forget about it until the next board comes. */
3926         newGameMode = IcsIdle;
3927         break;
3928       case RELATION_STARTING_POSITION:
3929         newGameMode = gameMode;
3930         break;
3931     }
3932
3933     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3934          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3935       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3936       char *toSqr;
3937       for (k = 0; k < ranks; k++) {
3938         for (j = 0; j < files; j++)
3939           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3940         if(gameInfo.holdingsWidth > 1) {
3941              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3942              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3943         }
3944       }
3945       CopyBoard(partnerBoard, board);
3946       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3947         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3948         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3949       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3950       if(toSqr = strchr(str, '-')) {
3951         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3952         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3953       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3954       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3955       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3956       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3957       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3958       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3959                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3960       DisplayMessage(partnerStatus, "");
3961         partnerBoardValid = TRUE;
3962       return;
3963     }
3964
3965     /* Modify behavior for initial board display on move listing
3966        of wild games.
3967        */
3968     switch (ics_getting_history) {
3969       case H_FALSE:
3970       case H_REQUESTED:
3971         break;
3972       case H_GOT_REQ_HEADER:
3973       case H_GOT_UNREQ_HEADER:
3974         /* This is the initial position of the current game */
3975         gamenum = ics_gamenum;
3976         moveNum = 0;            /* old ICS bug workaround */
3977         if (to_play == 'B') {
3978           startedFromSetupPosition = TRUE;
3979           blackPlaysFirst = TRUE;
3980           moveNum = 1;
3981           if (forwardMostMove == 0) forwardMostMove = 1;
3982           if (backwardMostMove == 0) backwardMostMove = 1;
3983           if (currentMove == 0) currentMove = 1;
3984         }
3985         newGameMode = gameMode;
3986         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3987         break;
3988       case H_GOT_UNWANTED_HEADER:
3989         /* This is an initial board that we don't want */
3990         return;
3991       case H_GETTING_MOVES:
3992         /* Should not happen */
3993         DisplayError(_("Error gathering move list: extra board"), 0);
3994         ics_getting_history = H_FALSE;
3995         return;
3996     }
3997
3998    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3999                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4000      /* [HGM] We seem to have switched variant unexpectedly
4001       * Try to guess new variant from board size
4002       */
4003           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4004           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4005           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4006           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4007           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4008           if(!weird) newVariant = VariantNormal;
4009           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4010           /* Get a move list just to see the header, which
4011              will tell us whether this is really bug or zh */
4012           if (ics_getting_history == H_FALSE) {
4013             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4014             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4015             SendToICS(str);
4016           }
4017     }
4018
4019     /* Take action if this is the first board of a new game, or of a
4020        different game than is currently being displayed.  */
4021     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4022         relation == RELATION_ISOLATED_BOARD) {
4023
4024         /* Forget the old game and get the history (if any) of the new one */
4025         if (gameMode != BeginningOfGame) {
4026           Reset(TRUE, TRUE);
4027         }
4028         newGame = TRUE;
4029         if (appData.autoRaiseBoard) BoardToTop();
4030         prevMove = -3;
4031         if (gamenum == -1) {
4032             newGameMode = IcsIdle;
4033         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4034                    appData.getMoveList && !reqFlag) {
4035             /* Need to get game history */
4036             ics_getting_history = H_REQUESTED;
4037             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4038             SendToICS(str);
4039         }
4040
4041         /* Initially flip the board to have black on the bottom if playing
4042            black or if the ICS flip flag is set, but let the user change
4043            it with the Flip View button. */
4044         flipView = appData.autoFlipView ?
4045           (newGameMode == IcsPlayingBlack) || ics_flip :
4046           appData.flipView;
4047
4048         /* Done with values from previous mode; copy in new ones */
4049         gameMode = newGameMode;
4050         ModeHighlight();
4051         ics_gamenum = gamenum;
4052         if (gamenum == gs_gamenum) {
4053             int klen = strlen(gs_kind);
4054             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4055             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4056             gameInfo.event = StrSave(str);
4057         } else {
4058             gameInfo.event = StrSave("ICS game");
4059         }
4060         gameInfo.site = StrSave(appData.icsHost);
4061         gameInfo.date = PGNDate();
4062         gameInfo.round = StrSave("-");
4063         gameInfo.white = StrSave(white);
4064         gameInfo.black = StrSave(black);
4065         timeControl = basetime * 60 * 1000;
4066         timeControl_2 = 0;
4067         timeIncrement = increment * 1000;
4068         movesPerSession = 0;
4069         gameInfo.timeControl = TimeControlTagValue();
4070         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4071   if (appData.debugMode) {
4072     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4073     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4074     setbuf(debugFP, NULL);
4075   }
4076
4077         gameInfo.outOfBook = NULL;
4078
4079         /* Do we have the ratings? */
4080         if (strcmp(player1Name, white) == 0 &&
4081             strcmp(player2Name, black) == 0) {
4082             if (appData.debugMode)
4083               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4084                       player1Rating, player2Rating);
4085             gameInfo.whiteRating = player1Rating;
4086             gameInfo.blackRating = player2Rating;
4087         } else if (strcmp(player2Name, white) == 0 &&
4088                    strcmp(player1Name, black) == 0) {
4089             if (appData.debugMode)
4090               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4091                       player2Rating, player1Rating);
4092             gameInfo.whiteRating = player2Rating;
4093             gameInfo.blackRating = player1Rating;
4094         }
4095         player1Name[0] = player2Name[0] = NULLCHAR;
4096
4097         /* Silence shouts if requested */
4098         if (appData.quietPlay &&
4099             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4100             SendToICS(ics_prefix);
4101             SendToICS("set shout 0\n");
4102         }
4103     }
4104
4105     /* Deal with midgame name changes */
4106     if (!newGame) {
4107         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4108             if (gameInfo.white) free(gameInfo.white);
4109             gameInfo.white = StrSave(white);
4110         }
4111         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4112             if (gameInfo.black) free(gameInfo.black);
4113             gameInfo.black = StrSave(black);
4114         }
4115     }
4116
4117     /* Throw away game result if anything actually changes in examine mode */
4118     if (gameMode == IcsExamining && !newGame) {
4119         gameInfo.result = GameUnfinished;
4120         if (gameInfo.resultDetails != NULL) {
4121             free(gameInfo.resultDetails);
4122             gameInfo.resultDetails = NULL;
4123         }
4124     }
4125
4126     /* In pausing && IcsExamining mode, we ignore boards coming
4127        in if they are in a different variation than we are. */
4128     if (pauseExamInvalid) return;
4129     if (pausing && gameMode == IcsExamining) {
4130         if (moveNum <= pauseExamForwardMostMove) {
4131             pauseExamInvalid = TRUE;
4132             forwardMostMove = pauseExamForwardMostMove;
4133             return;
4134         }
4135     }
4136
4137   if (appData.debugMode) {
4138     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4139   }
4140     /* Parse the board */
4141     for (k = 0; k < ranks; k++) {
4142       for (j = 0; j < files; j++)
4143         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4144       if(gameInfo.holdingsWidth > 1) {
4145            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4146            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4147       }
4148     }
4149     CopyBoard(boards[moveNum], board);
4150     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4151     if (moveNum == 0) {
4152         startedFromSetupPosition =
4153           !CompareBoards(board, initialPosition);
4154         if(startedFromSetupPosition)
4155             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4156     }
4157
4158     /* [HGM] Set castling rights. Take the outermost Rooks,
4159        to make it also work for FRC opening positions. Note that board12
4160        is really defective for later FRC positions, as it has no way to
4161        indicate which Rook can castle if they are on the same side of King.
4162        For the initial position we grant rights to the outermost Rooks,
4163        and remember thos rights, and we then copy them on positions
4164        later in an FRC game. This means WB might not recognize castlings with
4165        Rooks that have moved back to their original position as illegal,
4166        but in ICS mode that is not its job anyway.
4167     */
4168     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4169     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4170
4171         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4172             if(board[0][i] == WhiteRook) j = i;
4173         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4174         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4175             if(board[0][i] == WhiteRook) j = i;
4176         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4177         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4178             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4179         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4180         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4181             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4182         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4183
4184         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4185         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4186             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4187         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4188             if(board[BOARD_HEIGHT-1][k] == bKing)
4189                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4190         if(gameInfo.variant == VariantTwoKings) {
4191             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4192             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4193             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4194         }
4195     } else { int r;
4196         r = boards[moveNum][CASTLING][0] = initialRights[0];
4197         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4198         r = boards[moveNum][CASTLING][1] = initialRights[1];
4199         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4200         r = boards[moveNum][CASTLING][3] = initialRights[3];
4201         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4202         r = boards[moveNum][CASTLING][4] = initialRights[4];
4203         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4204         /* wildcastle kludge: always assume King has rights */
4205         r = boards[moveNum][CASTLING][2] = initialRights[2];
4206         r = boards[moveNum][CASTLING][5] = initialRights[5];
4207     }
4208     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4209     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4210
4211
4212     if (ics_getting_history == H_GOT_REQ_HEADER ||
4213         ics_getting_history == H_GOT_UNREQ_HEADER) {
4214         /* This was an initial position from a move list, not
4215            the current position */
4216         return;
4217     }
4218
4219     /* Update currentMove and known move number limits */
4220     newMove = newGame || moveNum > forwardMostMove;
4221
4222     if (newGame) {
4223         forwardMostMove = backwardMostMove = currentMove = moveNum;
4224         if (gameMode == IcsExamining && moveNum == 0) {
4225           /* Workaround for ICS limitation: we are not told the wild
4226              type when starting to examine a game.  But if we ask for
4227              the move list, the move list header will tell us */
4228             ics_getting_history = H_REQUESTED;
4229             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4230             SendToICS(str);
4231         }
4232     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4233                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4234 #if ZIPPY
4235         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4236         /* [HGM] applied this also to an engine that is silently watching        */
4237         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4238             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4239             gameInfo.variant == currentlyInitializedVariant) {
4240           takeback = forwardMostMove - moveNum;
4241           for (i = 0; i < takeback; i++) {
4242             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4243             SendToProgram("undo\n", &first);
4244           }
4245         }
4246 #endif
4247
4248         forwardMostMove = moveNum;
4249         if (!pausing || currentMove > forwardMostMove)
4250           currentMove = forwardMostMove;
4251     } else {
4252         /* New part of history that is not contiguous with old part */
4253         if (pausing && gameMode == IcsExamining) {
4254             pauseExamInvalid = TRUE;
4255             forwardMostMove = pauseExamForwardMostMove;
4256             return;
4257         }
4258         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4259 #if ZIPPY
4260             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4261                 // [HGM] when we will receive the move list we now request, it will be
4262                 // fed to the engine from the first move on. So if the engine is not
4263                 // in the initial position now, bring it there.
4264                 InitChessProgram(&first, 0);
4265             }
4266 #endif
4267             ics_getting_history = H_REQUESTED;
4268             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4269             SendToICS(str);
4270         }
4271         forwardMostMove = backwardMostMove = currentMove = moveNum;
4272     }
4273
4274     /* Update the clocks */
4275     if (strchr(elapsed_time, '.')) {
4276       /* Time is in ms */
4277       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4278       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4279     } else {
4280       /* Time is in seconds */
4281       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4282       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4283     }
4284
4285
4286 #if ZIPPY
4287     if (appData.zippyPlay && newGame &&
4288         gameMode != IcsObserving && gameMode != IcsIdle &&
4289         gameMode != IcsExamining)
4290       ZippyFirstBoard(moveNum, basetime, increment);
4291 #endif
4292
4293     /* Put the move on the move list, first converting
4294        to canonical algebraic form. */
4295     if (moveNum > 0) {
4296   if (appData.debugMode) {
4297     if (appData.debugMode) { int f = forwardMostMove;
4298         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4299                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4300                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4301     }
4302     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4303     fprintf(debugFP, "moveNum = %d\n", moveNum);
4304     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4305     setbuf(debugFP, NULL);
4306   }
4307         if (moveNum <= backwardMostMove) {
4308             /* We don't know what the board looked like before
4309                this move.  Punt. */
4310           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4311             strcat(parseList[moveNum - 1], " ");
4312             strcat(parseList[moveNum - 1], elapsed_time);
4313             moveList[moveNum - 1][0] = NULLCHAR;
4314         } else if (strcmp(move_str, "none") == 0) {
4315             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4316             /* Again, we don't know what the board looked like;
4317                this is really the start of the game. */
4318             parseList[moveNum - 1][0] = NULLCHAR;
4319             moveList[moveNum - 1][0] = NULLCHAR;
4320             backwardMostMove = moveNum;
4321             startedFromSetupPosition = TRUE;
4322             fromX = fromY = toX = toY = -1;
4323         } else {
4324           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4325           //                 So we parse the long-algebraic move string in stead of the SAN move
4326           int valid; char buf[MSG_SIZ], *prom;
4327
4328           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4329                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4330           // str looks something like "Q/a1-a2"; kill the slash
4331           if(str[1] == '/')
4332             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4333           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4334           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4335                 strcat(buf, prom); // long move lacks promo specification!
4336           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4337                 if(appData.debugMode)
4338                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4339                 safeStrCpy(move_str, buf, MSG_SIZ);
4340           }
4341           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4342                                 &fromX, &fromY, &toX, &toY, &promoChar)
4343                || ParseOneMove(buf, moveNum - 1, &moveType,
4344                                 &fromX, &fromY, &toX, &toY, &promoChar);
4345           // end of long SAN patch
4346           if (valid) {
4347             (void) CoordsToAlgebraic(boards[moveNum - 1],
4348                                      PosFlags(moveNum - 1),
4349                                      fromY, fromX, toY, toX, promoChar,
4350                                      parseList[moveNum-1]);
4351             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4352               case MT_NONE:
4353               case MT_STALEMATE:
4354               default:
4355                 break;
4356               case MT_CHECK:
4357                 if(gameInfo.variant != VariantShogi)
4358                     strcat(parseList[moveNum - 1], "+");
4359                 break;
4360               case MT_CHECKMATE:
4361               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4362                 strcat(parseList[moveNum - 1], "#");
4363                 break;
4364             }
4365             strcat(parseList[moveNum - 1], " ");
4366             strcat(parseList[moveNum - 1], elapsed_time);
4367             /* currentMoveString is set as a side-effect of ParseOneMove */
4368             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4369             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4370             strcat(moveList[moveNum - 1], "\n");
4371
4372             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4373                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4374               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4375                 ChessSquare old, new = boards[moveNum][k][j];
4376                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4377                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4378                   if(old == new) continue;
4379                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4380                   else if(new == WhiteWazir || new == BlackWazir) {
4381                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4382                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4383                       else boards[moveNum][k][j] = old; // preserve type of Gold
4384                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4385                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4386               }
4387           } else {
4388             /* Move from ICS was illegal!?  Punt. */
4389             if (appData.debugMode) {
4390               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4391               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4392             }
4393             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4394             strcat(parseList[moveNum - 1], " ");
4395             strcat(parseList[moveNum - 1], elapsed_time);
4396             moveList[moveNum - 1][0] = NULLCHAR;
4397             fromX = fromY = toX = toY = -1;
4398           }
4399         }
4400   if (appData.debugMode) {
4401     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4402     setbuf(debugFP, NULL);
4403   }
4404
4405 #if ZIPPY
4406         /* Send move to chess program (BEFORE animating it). */
4407         if (appData.zippyPlay && !newGame && newMove &&
4408            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4409
4410             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4411                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4412                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4413                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4414                             move_str);
4415                     DisplayError(str, 0);
4416                 } else {
4417                     if (first.sendTime) {
4418                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4419                     }
4420                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4421                     if (firstMove && !bookHit) {
4422                         firstMove = FALSE;
4423                         if (first.useColors) {
4424                           SendToProgram(gameMode == IcsPlayingWhite ?
4425                                         "white\ngo\n" :
4426                                         "black\ngo\n", &first);
4427                         } else {
4428                           SendToProgram("go\n", &first);
4429                         }
4430                         first.maybeThinking = TRUE;
4431                     }
4432                 }
4433             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4434               if (moveList[moveNum - 1][0] == NULLCHAR) {
4435                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4436                 DisplayError(str, 0);
4437               } else {
4438                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4439                 SendMoveToProgram(moveNum - 1, &first);
4440               }
4441             }
4442         }
4443 #endif
4444     }
4445
4446     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4447         /* If move comes from a remote source, animate it.  If it
4448            isn't remote, it will have already been animated. */
4449         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4450             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4451         }
4452         if (!pausing && appData.highlightLastMove) {
4453             SetHighlights(fromX, fromY, toX, toY);
4454         }
4455     }
4456
4457     /* Start the clocks */
4458     whiteFlag = blackFlag = FALSE;
4459     appData.clockMode = !(basetime == 0 && increment == 0);
4460     if (ticking == 0) {
4461       ics_clock_paused = TRUE;
4462       StopClocks();
4463     } else if (ticking == 1) {
4464       ics_clock_paused = FALSE;
4465     }
4466     if (gameMode == IcsIdle ||
4467         relation == RELATION_OBSERVING_STATIC ||
4468         relation == RELATION_EXAMINING ||
4469         ics_clock_paused)
4470       DisplayBothClocks();
4471     else
4472       StartClocks();
4473
4474     /* Display opponents and material strengths */
4475     if (gameInfo.variant != VariantBughouse &&
4476         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4477         if (tinyLayout || smallLayout) {
4478             if(gameInfo.variant == VariantNormal)
4479               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4480                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4481                     basetime, increment);
4482             else
4483               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4484                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4485                     basetime, increment, (int) gameInfo.variant);
4486         } else {
4487             if(gameInfo.variant == VariantNormal)
4488               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4489                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4490                     basetime, increment);
4491             else
4492               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4493                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4494                     basetime, increment, VariantName(gameInfo.variant));
4495         }
4496         DisplayTitle(str);
4497   if (appData.debugMode) {
4498     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4499   }
4500     }
4501
4502
4503     /* Display the board */
4504     if (!pausing && !appData.noGUI) {
4505
4506       if (appData.premove)
4507           if (!gotPremove ||
4508              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4509              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4510               ClearPremoveHighlights();
4511
4512       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4513         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4514       DrawPosition(j, boards[currentMove]);
4515
4516       DisplayMove(moveNum - 1);
4517       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4518             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4519               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4520         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4521       }
4522     }
4523
4524     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4525 #if ZIPPY
4526     if(bookHit) { // [HGM] book: simulate book reply
4527         static char bookMove[MSG_SIZ]; // a bit generous?
4528
4529         programStats.nodes = programStats.depth = programStats.time =
4530         programStats.score = programStats.got_only_move = 0;
4531         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4532
4533         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4534         strcat(bookMove, bookHit);
4535         HandleMachineMove(bookMove, &first);
4536     }
4537 #endif
4538 }
4539
4540 void
4541 GetMoveListEvent()
4542 {
4543     char buf[MSG_SIZ];
4544     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4545         ics_getting_history = H_REQUESTED;
4546         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4547         SendToICS(buf);
4548     }
4549 }
4550
4551 void
4552 AnalysisPeriodicEvent(force)
4553      int force;
4554 {
4555     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4556          && !force) || !appData.periodicUpdates)
4557       return;
4558
4559     /* Send . command to Crafty to collect stats */
4560     SendToProgram(".\n", &first);
4561
4562     /* Don't send another until we get a response (this makes
4563        us stop sending to old Crafty's which don't understand
4564        the "." command (sending illegal cmds resets node count & time,
4565        which looks bad)) */
4566     programStats.ok_to_send = 0;
4567 }
4568
4569 void ics_update_width(new_width)
4570         int new_width;
4571 {
4572         ics_printf("set width %d\n", new_width);
4573 }
4574
4575 void
4576 SendMoveToProgram(moveNum, cps)
4577      int moveNum;
4578      ChessProgramState *cps;
4579 {
4580     char buf[MSG_SIZ];
4581
4582     if (cps->useUsermove) {
4583       SendToProgram("usermove ", cps);
4584     }
4585     if (cps->useSAN) {
4586       char *space;
4587       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4588         int len = space - parseList[moveNum];
4589         memcpy(buf, parseList[moveNum], len);
4590         buf[len++] = '\n';
4591         buf[len] = NULLCHAR;
4592       } else {
4593         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4594       }
4595       SendToProgram(buf, cps);
4596     } else {
4597       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4598         AlphaRank(moveList[moveNum], 4);
4599         SendToProgram(moveList[moveNum], cps);
4600         AlphaRank(moveList[moveNum], 4); // and back
4601       } else
4602       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4603        * the engine. It would be nice to have a better way to identify castle
4604        * moves here. */
4605       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4606                                                                          && cps->useOOCastle) {
4607         int fromX = moveList[moveNum][0] - AAA;
4608         int fromY = moveList[moveNum][1] - ONE;
4609         int toX = moveList[moveNum][2] - AAA;
4610         int toY = moveList[moveNum][3] - ONE;
4611         if((boards[moveNum][fromY][fromX] == WhiteKing
4612             && boards[moveNum][toY][toX] == WhiteRook)
4613            || (boards[moveNum][fromY][fromX] == BlackKing
4614                && boards[moveNum][toY][toX] == BlackRook)) {
4615           if(toX > fromX) SendToProgram("O-O\n", cps);
4616           else SendToProgram("O-O-O\n", cps);
4617         }
4618         else SendToProgram(moveList[moveNum], cps);
4619       }
4620       else SendToProgram(moveList[moveNum], cps);
4621       /* End of additions by Tord */
4622     }
4623
4624     /* [HGM] setting up the opening has brought engine in force mode! */
4625     /*       Send 'go' if we are in a mode where machine should play. */
4626     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4627         (gameMode == TwoMachinesPlay   ||
4628 #if ZIPPY
4629          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4630 #endif
4631          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4632         SendToProgram("go\n", cps);
4633   if (appData.debugMode) {
4634     fprintf(debugFP, "(extra)\n");
4635   }
4636     }
4637     setboardSpoiledMachineBlack = 0;
4638 }
4639
4640 void
4641 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4642      ChessMove moveType;
4643      int fromX, fromY, toX, toY;
4644      char promoChar;
4645 {
4646     char user_move[MSG_SIZ];
4647
4648     switch (moveType) {
4649       default:
4650         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4651                 (int)moveType, fromX, fromY, toX, toY);
4652         DisplayError(user_move + strlen("say "), 0);
4653         break;
4654       case WhiteKingSideCastle:
4655       case BlackKingSideCastle:
4656       case WhiteQueenSideCastleWild:
4657       case BlackQueenSideCastleWild:
4658       /* PUSH Fabien */
4659       case WhiteHSideCastleFR:
4660       case BlackHSideCastleFR:
4661       /* POP Fabien */
4662         snprintf(user_move, MSG_SIZ, "o-o\n");
4663         break;
4664       case WhiteQueenSideCastle:
4665       case BlackQueenSideCastle:
4666       case WhiteKingSideCastleWild:
4667       case BlackKingSideCastleWild:
4668       /* PUSH Fabien */
4669       case WhiteASideCastleFR:
4670       case BlackASideCastleFR:
4671       /* POP Fabien */
4672         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4673         break;
4674       case WhiteNonPromotion:
4675       case BlackNonPromotion:
4676         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4677         break;
4678       case WhitePromotion:
4679       case BlackPromotion:
4680         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4681           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4682                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4683                 PieceToChar(WhiteFerz));
4684         else if(gameInfo.variant == VariantGreat)
4685           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4686                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4687                 PieceToChar(WhiteMan));
4688         else
4689           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4690                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4691                 promoChar);
4692         break;
4693       case WhiteDrop:
4694       case BlackDrop:
4695       drop:
4696         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4697                  ToUpper(PieceToChar((ChessSquare) fromX)),
4698                  AAA + toX, ONE + toY);
4699         break;
4700       case IllegalMove:  /* could be a variant we don't quite understand */
4701         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4702       case NormalMove:
4703       case WhiteCapturesEnPassant:
4704       case BlackCapturesEnPassant:
4705         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4706                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4707         break;
4708     }
4709     SendToICS(user_move);
4710     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4711         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4712 }
4713
4714 void
4715 UploadGameEvent()
4716 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4717     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4718     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4719     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4720         DisplayError("You cannot do this while you are playing or observing", 0);
4721         return;
4722     }
4723     if(gameMode != IcsExamining) { // is this ever not the case?
4724         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4725
4726         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4727           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4728         } else { // on FICS we must first go to general examine mode
4729           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4730         }
4731         if(gameInfo.variant != VariantNormal) {
4732             // try figure out wild number, as xboard names are not always valid on ICS
4733             for(i=1; i<=36; i++) {
4734               snprintf(buf, MSG_SIZ, "wild/%d", i);
4735                 if(StringToVariant(buf) == gameInfo.variant) break;
4736             }
4737             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4738             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4739             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4740         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4741         SendToICS(ics_prefix);
4742         SendToICS(buf);
4743         if(startedFromSetupPosition || backwardMostMove != 0) {
4744           fen = PositionToFEN(backwardMostMove, NULL);
4745           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4746             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4747             SendToICS(buf);
4748           } else { // FICS: everything has to set by separate bsetup commands
4749             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4750             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4751             SendToICS(buf);
4752             if(!WhiteOnMove(backwardMostMove)) {
4753                 SendToICS("bsetup tomove black\n");
4754             }
4755             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4756             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4757             SendToICS(buf);
4758             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4759             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4760             SendToICS(buf);
4761             i = boards[backwardMostMove][EP_STATUS];
4762             if(i >= 0) { // set e.p.
4763               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4764                 SendToICS(buf);
4765             }
4766             bsetup++;
4767           }
4768         }
4769       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4770             SendToICS("bsetup done\n"); // switch to normal examining.
4771     }
4772     for(i = backwardMostMove; i<last; i++) {
4773         char buf[20];
4774         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4775         SendToICS(buf);
4776     }
4777     SendToICS(ics_prefix);
4778     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4779 }
4780
4781 void
4782 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4783      int rf, ff, rt, ft;
4784      char promoChar;
4785      char move[7];
4786 {
4787     if (rf == DROP_RANK) {
4788       sprintf(move, "%c@%c%c\n",
4789                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4790     } else {
4791         if (promoChar == 'x' || promoChar == NULLCHAR) {
4792           sprintf(move, "%c%c%c%c\n",
4793                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4794         } else {
4795             sprintf(move, "%c%c%c%c%c\n",
4796                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4797         }
4798     }
4799 }
4800
4801 void
4802 ProcessICSInitScript(f)
4803      FILE *f;
4804 {
4805     char buf[MSG_SIZ];
4806
4807     while (fgets(buf, MSG_SIZ, f)) {
4808         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4809     }
4810
4811     fclose(f);
4812 }
4813
4814
4815 void
4816 NextPiece(int step)
4817 {
4818     ChessSquare piece = boards[currentMove][toY][toX];
4819     do {
4820         pieceSweep += step;
4821         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4822         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4823         if(!step) step = 1;
4824     } while(PieceToChar(pieceSweep) == '.');
4825     boards[currentMove][toY][toX] = pieceSweep;
4826     DrawPosition(FALSE, boards[currentMove]);
4827     boards[currentMove][toY][toX] = piece;
4828 }
4829 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4830 void
4831 AlphaRank(char *move, int n)
4832 {
4833 //    char *p = move, c; int x, y;
4834
4835     if (appData.debugMode) {
4836         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4837     }
4838
4839     if(move[1]=='*' &&
4840        move[2]>='0' && move[2]<='9' &&
4841        move[3]>='a' && move[3]<='x'    ) {
4842         move[1] = '@';
4843         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4844         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4845     } else
4846     if(move[0]>='0' && move[0]<='9' &&
4847        move[1]>='a' && move[1]<='x' &&
4848        move[2]>='0' && move[2]<='9' &&
4849        move[3]>='a' && move[3]<='x'    ) {
4850         /* input move, Shogi -> normal */
4851         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4852         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4853         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4854         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4855     } else
4856     if(move[1]=='@' &&
4857        move[3]>='0' && move[3]<='9' &&
4858        move[2]>='a' && move[2]<='x'    ) {
4859         move[1] = '*';
4860         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4861         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4862     } else
4863     if(
4864        move[0]>='a' && move[0]<='x' &&
4865        move[3]>='0' && move[3]<='9' &&
4866        move[2]>='a' && move[2]<='x'    ) {
4867          /* output move, normal -> Shogi */
4868         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4869         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4870         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4871         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4872         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4873     }
4874     if (appData.debugMode) {
4875         fprintf(debugFP, "   out = '%s'\n", move);
4876     }
4877 }
4878
4879 char yy_textstr[8000];
4880
4881 /* Parser for moves from gnuchess, ICS, or user typein box */
4882 Boolean
4883 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4884      char *move;
4885      int moveNum;
4886      ChessMove *moveType;
4887      int *fromX, *fromY, *toX, *toY;
4888      char *promoChar;
4889 {
4890     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4891
4892     switch (*moveType) {
4893       case WhitePromotion:
4894       case BlackPromotion:
4895       case WhiteNonPromotion:
4896       case BlackNonPromotion:
4897       case NormalMove:
4898       case WhiteCapturesEnPassant:
4899       case BlackCapturesEnPassant:
4900       case WhiteKingSideCastle:
4901       case WhiteQueenSideCastle:
4902       case BlackKingSideCastle:
4903       case BlackQueenSideCastle:
4904       case WhiteKingSideCastleWild:
4905       case WhiteQueenSideCastleWild:
4906       case BlackKingSideCastleWild:
4907       case BlackQueenSideCastleWild:
4908       /* Code added by Tord: */
4909       case WhiteHSideCastleFR:
4910       case WhiteASideCastleFR:
4911       case BlackHSideCastleFR:
4912       case BlackASideCastleFR:
4913       /* End of code added by Tord */
4914       case IllegalMove:         /* bug or odd chess variant */
4915         *fromX = currentMoveString[0] - AAA;
4916         *fromY = currentMoveString[1] - ONE;
4917         *toX = currentMoveString[2] - AAA;
4918         *toY = currentMoveString[3] - ONE;
4919         *promoChar = currentMoveString[4];
4920         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4921             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4922     if (appData.debugMode) {
4923         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4924     }
4925             *fromX = *fromY = *toX = *toY = 0;
4926             return FALSE;
4927         }
4928         if (appData.testLegality) {
4929           return (*moveType != IllegalMove);
4930         } else {
4931           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4932                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4933         }
4934
4935       case WhiteDrop:
4936       case BlackDrop:
4937         *fromX = *moveType == WhiteDrop ?
4938           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4939           (int) CharToPiece(ToLower(currentMoveString[0]));
4940         *fromY = DROP_RANK;
4941         *toX = currentMoveString[2] - AAA;
4942         *toY = currentMoveString[3] - ONE;
4943         *promoChar = NULLCHAR;
4944         return TRUE;
4945
4946       case AmbiguousMove:
4947       case ImpossibleMove:
4948       case EndOfFile:
4949       case ElapsedTime:
4950       case Comment:
4951       case PGNTag:
4952       case NAG:
4953       case WhiteWins:
4954       case BlackWins:
4955       case GameIsDrawn:
4956       default:
4957     if (appData.debugMode) {
4958         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4959     }
4960         /* bug? */
4961         *fromX = *fromY = *toX = *toY = 0;
4962         *promoChar = NULLCHAR;
4963         return FALSE;
4964     }
4965 }
4966
4967
4968 void
4969 ParsePV(char *pv, Boolean storeComments)
4970 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4971   int fromX, fromY, toX, toY; char promoChar;
4972   ChessMove moveType;
4973   Boolean valid;
4974   int nr = 0;
4975
4976   endPV = forwardMostMove;
4977   do {
4978     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4979     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4980     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4981 if(appData.debugMode){
4982 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);
4983 }
4984     if(!valid && nr == 0 &&
4985        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4986         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4987         // Hande case where played move is different from leading PV move
4988         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4989         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4990         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4991         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4992           endPV += 2; // if position different, keep this
4993           moveList[endPV-1][0] = fromX + AAA;
4994           moveList[endPV-1][1] = fromY + ONE;
4995           moveList[endPV-1][2] = toX + AAA;
4996           moveList[endPV-1][3] = toY + ONE;
4997           parseList[endPV-1][0] = NULLCHAR;
4998           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4999         }
5000       }
5001     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5002     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5003     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5004     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5005         valid++; // allow comments in PV
5006         continue;
5007     }
5008     nr++;
5009     if(endPV+1 > framePtr) break; // no space, truncate
5010     if(!valid) break;
5011     endPV++;
5012     CopyBoard(boards[endPV], boards[endPV-1]);
5013     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5014     moveList[endPV-1][0] = fromX + AAA;
5015     moveList[endPV-1][1] = fromY + ONE;
5016     moveList[endPV-1][2] = toX + AAA;
5017     moveList[endPV-1][3] = toY + ONE;
5018     moveList[endPV-1][4] = promoChar;
5019     moveList[endPV-1][5] = NULLCHAR;
5020     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5021     if(storeComments)
5022         CoordsToAlgebraic(boards[endPV - 1],
5023                              PosFlags(endPV - 1),
5024                              fromY, fromX, toY, toX, promoChar,
5025                              parseList[endPV - 1]);
5026     else
5027         parseList[endPV-1][0] = NULLCHAR;
5028   } while(valid);
5029   currentMove = endPV;
5030   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5031   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5032                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5033   DrawPosition(TRUE, boards[currentMove]);
5034 }
5035
5036 static int lastX, lastY;
5037
5038 Boolean
5039 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5040 {
5041         int startPV;
5042         char *p;
5043
5044         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5045         lastX = x; lastY = y;
5046         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5047         startPV = index;
5048         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5049         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5050         index = startPV;
5051         do{ while(buf[index] && buf[index] != '\n') index++;
5052         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5053         buf[index] = 0;
5054         ParsePV(buf+startPV, FALSE);
5055         *start = startPV; *end = index-1;
5056         return TRUE;
5057 }
5058
5059 Boolean
5060 LoadPV(int x, int y)
5061 { // called on right mouse click to load PV
5062   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5063   lastX = x; lastY = y;
5064   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5065   return TRUE;
5066 }
5067
5068 void
5069 UnLoadPV()
5070 {
5071   if(endPV < 0) return;
5072   endPV = -1;
5073   currentMove = forwardMostMove;
5074   ClearPremoveHighlights();
5075   DrawPosition(TRUE, boards[currentMove]);
5076 }
5077
5078 void
5079 MovePV(int x, int y, int h)
5080 { // step through PV based on mouse coordinates (called on mouse move)
5081   int margin = h>>3, step = 0, dist;
5082
5083   // we must somehow check if right button is still down (might be released off board!)
5084   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5085   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5086   if(!step) return;
5087   lastX = x; lastY = y;
5088
5089   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5090   if(endPV < 0) return;
5091   if(y < margin) step = 1; else
5092   if(y > h - margin) step = -1;
5093   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5094   currentMove += step;
5095   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5096   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5097                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5098   DrawPosition(FALSE, boards[currentMove]);
5099 }
5100
5101
5102 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5103 // All positions will have equal probability, but the current method will not provide a unique
5104 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5105 #define DARK 1
5106 #define LITE 2
5107 #define ANY 3
5108
5109 int squaresLeft[4];
5110 int piecesLeft[(int)BlackPawn];
5111 int seed, nrOfShuffles;
5112
5113 void GetPositionNumber()
5114 {       // sets global variable seed
5115         int i;
5116
5117         seed = appData.defaultFrcPosition;
5118         if(seed < 0) { // randomize based on time for negative FRC position numbers
5119                 for(i=0; i<50; i++) seed += random();
5120                 seed = random() ^ random() >> 8 ^ random() << 8;
5121                 if(seed<0) seed = -seed;
5122         }
5123 }
5124
5125 int put(Board board, int pieceType, int rank, int n, int shade)
5126 // put the piece on the (n-1)-th empty squares of the given shade
5127 {
5128         int i;
5129
5130         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5131                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5132                         board[rank][i] = (ChessSquare) pieceType;
5133                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5134                         squaresLeft[ANY]--;
5135                         piecesLeft[pieceType]--;
5136                         return i;
5137                 }
5138         }
5139         return -1;
5140 }
5141
5142
5143 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5144 // calculate where the next piece goes, (any empty square), and put it there
5145 {
5146         int i;
5147
5148         i = seed % squaresLeft[shade];
5149         nrOfShuffles *= squaresLeft[shade];
5150         seed /= squaresLeft[shade];
5151         put(board, pieceType, rank, i, shade);
5152 }
5153
5154 void AddTwoPieces(Board board, int pieceType, int rank)
5155 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5156 {
5157         int i, n=squaresLeft[ANY], j=n-1, k;
5158
5159         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5160         i = seed % k;  // pick one
5161         nrOfShuffles *= k;
5162         seed /= k;
5163         while(i >= j) i -= j--;
5164         j = n - 1 - j; i += j;
5165         put(board, pieceType, rank, j, ANY);
5166         put(board, pieceType, rank, i, ANY);
5167 }
5168
5169 void SetUpShuffle(Board board, int number)
5170 {
5171         int i, p, first=1;
5172
5173         GetPositionNumber(); nrOfShuffles = 1;
5174
5175         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5176         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5177         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5178
5179         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5180
5181         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5182             p = (int) board[0][i];
5183             if(p < (int) BlackPawn) piecesLeft[p] ++;
5184             board[0][i] = EmptySquare;
5185         }
5186
5187         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5188             // shuffles restricted to allow normal castling put KRR first
5189             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5190                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5191             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5192                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5193             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5194                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5195             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5196                 put(board, WhiteRook, 0, 0, ANY);
5197             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5198         }
5199
5200         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5201             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5202             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5203                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5204                 while(piecesLeft[p] >= 2) {
5205                     AddOnePiece(board, p, 0, LITE);
5206                     AddOnePiece(board, p, 0, DARK);
5207                 }
5208                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5209             }
5210
5211         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5212             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5213             // but we leave King and Rooks for last, to possibly obey FRC restriction
5214             if(p == (int)WhiteRook) continue;
5215             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5216             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5217         }
5218
5219         // now everything is placed, except perhaps King (Unicorn) and Rooks
5220
5221         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5222             // Last King gets castling rights
5223             while(piecesLeft[(int)WhiteUnicorn]) {
5224                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5225                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5226             }
5227
5228             while(piecesLeft[(int)WhiteKing]) {
5229                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5230                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5231             }
5232
5233
5234         } else {
5235             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5236             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5237         }
5238
5239         // Only Rooks can be left; simply place them all
5240         while(piecesLeft[(int)WhiteRook]) {
5241                 i = put(board, WhiteRook, 0, 0, ANY);
5242                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5243                         if(first) {
5244                                 first=0;
5245                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5246                         }
5247                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5248                 }
5249         }
5250         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5251             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5252         }
5253
5254         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5255 }
5256
5257 int SetCharTable( char *table, const char * map )
5258 /* [HGM] moved here from winboard.c because of its general usefulness */
5259 /*       Basically a safe strcpy that uses the last character as King */
5260 {
5261     int result = FALSE; int NrPieces;
5262
5263     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5264                     && NrPieces >= 12 && !(NrPieces&1)) {
5265         int i; /* [HGM] Accept even length from 12 to 34 */
5266
5267         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5268         for( i=0; i<NrPieces/2-1; i++ ) {
5269             table[i] = map[i];
5270             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5271         }
5272         table[(int) WhiteKing]  = map[NrPieces/2-1];
5273         table[(int) BlackKing]  = map[NrPieces-1];
5274
5275         result = TRUE;
5276     }
5277
5278     return result;
5279 }
5280
5281 void Prelude(Board board)
5282 {       // [HGM] superchess: random selection of exo-pieces
5283         int i, j, k; ChessSquare p;
5284         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5285
5286         GetPositionNumber(); // use FRC position number
5287
5288         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5289             SetCharTable(pieceToChar, appData.pieceToCharTable);
5290             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5291                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5292         }
5293
5294         j = seed%4;                 seed /= 4;
5295         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5296         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5297         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5298         j = seed%3 + (seed%3 >= j); seed /= 3;
5299         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5300         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5301         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5302         j = seed%3;                 seed /= 3;
5303         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5304         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5305         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5306         j = seed%2 + (seed%2 >= j); seed /= 2;
5307         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5308         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5309         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5310         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5311         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5312         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5313         put(board, exoPieces[0],    0, 0, ANY);
5314         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5315 }
5316
5317 void
5318 InitPosition(redraw)
5319      int redraw;
5320 {
5321     ChessSquare (* pieces)[BOARD_FILES];
5322     int i, j, pawnRow, overrule,
5323     oldx = gameInfo.boardWidth,
5324     oldy = gameInfo.boardHeight,
5325     oldh = gameInfo.holdingsWidth;
5326     static int oldv;
5327
5328     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5329
5330     /* [AS] Initialize pv info list [HGM] and game status */
5331     {
5332         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5333             pvInfoList[i].depth = 0;
5334             boards[i][EP_STATUS] = EP_NONE;
5335             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5336         }
5337
5338         initialRulePlies = 0; /* 50-move counter start */
5339
5340         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5341         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5342     }
5343
5344
5345     /* [HGM] logic here is completely changed. In stead of full positions */
5346     /* the initialized data only consist of the two backranks. The switch */
5347     /* selects which one we will use, which is than copied to the Board   */
5348     /* initialPosition, which for the rest is initialized by Pawns and    */
5349     /* empty squares. This initial position is then copied to boards[0],  */
5350     /* possibly after shuffling, so that it remains available.            */
5351
5352     gameInfo.holdingsWidth = 0; /* default board sizes */
5353     gameInfo.boardWidth    = 8;
5354     gameInfo.boardHeight   = 8;
5355     gameInfo.holdingsSize  = 0;
5356     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5357     for(i=0; i<BOARD_FILES-2; i++)
5358       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5359     initialPosition[EP_STATUS] = EP_NONE;
5360     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5361     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5362          SetCharTable(pieceNickName, appData.pieceNickNames);
5363     else SetCharTable(pieceNickName, "............");
5364     pieces = FIDEArray;
5365
5366     switch (gameInfo.variant) {
5367     case VariantFischeRandom:
5368       shuffleOpenings = TRUE;
5369     default:
5370       break;
5371     case VariantShatranj:
5372       pieces = ShatranjArray;
5373       nrCastlingRights = 0;
5374       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5375       break;
5376     case VariantMakruk:
5377       pieces = makrukArray;
5378       nrCastlingRights = 0;
5379       startedFromSetupPosition = TRUE;
5380       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5381       break;
5382     case VariantTwoKings:
5383       pieces = twoKingsArray;
5384       break;
5385     case VariantCapaRandom:
5386       shuffleOpenings = TRUE;
5387     case VariantCapablanca:
5388       pieces = CapablancaArray;
5389       gameInfo.boardWidth = 10;
5390       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5391       break;
5392     case VariantGothic:
5393       pieces = GothicArray;
5394       gameInfo.boardWidth = 10;
5395       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5396       break;
5397     case VariantSChess:
5398       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5399       gameInfo.holdingsSize = 7;
5400       break;
5401     case VariantJanus:
5402       pieces = JanusArray;
5403       gameInfo.boardWidth = 10;
5404       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5405       nrCastlingRights = 6;
5406         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5407         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5408         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5409         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5410         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5411         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5412       break;
5413     case VariantFalcon:
5414       pieces = FalconArray;
5415       gameInfo.boardWidth = 10;
5416       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5417       break;
5418     case VariantXiangqi:
5419       pieces = XiangqiArray;
5420       gameInfo.boardWidth  = 9;
5421       gameInfo.boardHeight = 10;
5422       nrCastlingRights = 0;
5423       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5424       break;
5425     case VariantShogi:
5426       pieces = ShogiArray;
5427       gameInfo.boardWidth  = 9;
5428       gameInfo.boardHeight = 9;
5429       gameInfo.holdingsSize = 7;
5430       nrCastlingRights = 0;
5431       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5432       break;
5433     case VariantCourier:
5434       pieces = CourierArray;
5435       gameInfo.boardWidth  = 12;
5436       nrCastlingRights = 0;
5437       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5438       break;
5439     case VariantKnightmate:
5440       pieces = KnightmateArray;
5441       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5442       break;
5443     case VariantSpartan:
5444       pieces = SpartanArray;
5445       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5446       break;
5447     case VariantFairy:
5448       pieces = fairyArray;
5449       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5450       break;
5451     case VariantGreat:
5452       pieces = GreatArray;
5453       gameInfo.boardWidth = 10;
5454       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5455       gameInfo.holdingsSize = 8;
5456       break;
5457     case VariantSuper:
5458       pieces = FIDEArray;
5459       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5460       gameInfo.holdingsSize = 8;
5461       startedFromSetupPosition = TRUE;
5462       break;
5463     case VariantCrazyhouse:
5464     case VariantBughouse:
5465       pieces = FIDEArray;
5466       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5467       gameInfo.holdingsSize = 5;
5468       break;
5469     case VariantWildCastle:
5470       pieces = FIDEArray;
5471       /* !!?shuffle with kings guaranteed to be on d or e file */
5472       shuffleOpenings = 1;
5473       break;
5474     case VariantNoCastle:
5475       pieces = FIDEArray;
5476       nrCastlingRights = 0;
5477       /* !!?unconstrained back-rank shuffle */
5478       shuffleOpenings = 1;
5479       break;
5480     }
5481
5482     overrule = 0;
5483     if(appData.NrFiles >= 0) {
5484         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5485         gameInfo.boardWidth = appData.NrFiles;
5486     }
5487     if(appData.NrRanks >= 0) {
5488         gameInfo.boardHeight = appData.NrRanks;
5489     }
5490     if(appData.holdingsSize >= 0) {
5491         i = appData.holdingsSize;
5492         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5493         gameInfo.holdingsSize = i;
5494     }
5495     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5496     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5497         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5498
5499     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5500     if(pawnRow < 1) pawnRow = 1;
5501     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5502
5503     /* User pieceToChar list overrules defaults */
5504     if(appData.pieceToCharTable != NULL)
5505         SetCharTable(pieceToChar, appData.pieceToCharTable);
5506
5507     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5508
5509         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5510             s = (ChessSquare) 0; /* account holding counts in guard band */
5511         for( i=0; i<BOARD_HEIGHT; i++ )
5512             initialPosition[i][j] = s;
5513
5514         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5515         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5516         initialPosition[pawnRow][j] = WhitePawn;
5517         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5518         if(gameInfo.variant == VariantXiangqi) {
5519             if(j&1) {
5520                 initialPosition[pawnRow][j] =
5521                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5522                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5523                    initialPosition[2][j] = WhiteCannon;
5524                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5525                 }
5526             }
5527         }
5528         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5529     }
5530     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5531
5532             j=BOARD_LEFT+1;
5533             initialPosition[1][j] = WhiteBishop;
5534             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5535             j=BOARD_RGHT-2;
5536             initialPosition[1][j] = WhiteRook;
5537             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5538     }
5539
5540     if( nrCastlingRights == -1) {
5541         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5542         /*       This sets default castling rights from none to normal corners   */
5543         /* Variants with other castling rights must set them themselves above    */
5544         nrCastlingRights = 6;
5545
5546         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5547         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5548         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5549         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5550         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5551         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5552      }
5553
5554      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5555      if(gameInfo.variant == VariantGreat) { // promotion commoners
5556         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5557         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5558         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5559         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5560      }
5561      if( gameInfo.variant == VariantSChess ) {
5562       initialPosition[1][0] = BlackMarshall;
5563       initialPosition[2][0] = BlackAngel;
5564       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5565       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5566       initialPosition[1][1] = initialPosition[2][1] = 
5567       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5568      }
5569   if (appData.debugMode) {
5570     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5571   }
5572     if(shuffleOpenings) {
5573         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5574         startedFromSetupPosition = TRUE;
5575     }
5576     if(startedFromPositionFile) {
5577       /* [HGM] loadPos: use PositionFile for every new game */
5578       CopyBoard(initialPosition, filePosition);
5579       for(i=0; i<nrCastlingRights; i++)
5580           initialRights[i] = filePosition[CASTLING][i];
5581       startedFromSetupPosition = TRUE;
5582     }
5583
5584     CopyBoard(boards[0], initialPosition);
5585
5586     if(oldx != gameInfo.boardWidth ||
5587        oldy != gameInfo.boardHeight ||
5588        oldv != gameInfo.variant ||
5589        oldh != gameInfo.holdingsWidth
5590                                          )
5591             InitDrawingSizes(-2 ,0);
5592
5593     oldv = gameInfo.variant;
5594     if (redraw)
5595       DrawPosition(TRUE, boards[currentMove]);
5596 }
5597
5598 void
5599 SendBoard(cps, moveNum)
5600      ChessProgramState *cps;
5601      int moveNum;
5602 {
5603     char message[MSG_SIZ];
5604
5605     if (cps->useSetboard) {
5606       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5607       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5608       SendToProgram(message, cps);
5609       free(fen);
5610
5611     } else {
5612       ChessSquare *bp;
5613       int i, j;
5614       /* Kludge to set black to move, avoiding the troublesome and now
5615        * deprecated "black" command.
5616        */
5617       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5618         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5619
5620       SendToProgram("edit\n", cps);
5621       SendToProgram("#\n", cps);
5622       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5623         bp = &boards[moveNum][i][BOARD_LEFT];
5624         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5625           if ((int) *bp < (int) BlackPawn) {
5626             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5627                     AAA + j, ONE + i);
5628             if(message[0] == '+' || message[0] == '~') {
5629               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5630                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5631                         AAA + j, ONE + i);
5632             }
5633             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5634                 message[1] = BOARD_RGHT   - 1 - j + '1';
5635                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5636             }
5637             SendToProgram(message, cps);
5638           }
5639         }
5640       }
5641
5642       SendToProgram("c\n", cps);
5643       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5644         bp = &boards[moveNum][i][BOARD_LEFT];
5645         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5646           if (((int) *bp != (int) EmptySquare)
5647               && ((int) *bp >= (int) BlackPawn)) {
5648             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5649                     AAA + j, ONE + i);
5650             if(message[0] == '+' || message[0] == '~') {
5651               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5652                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5653                         AAA + j, ONE + i);
5654             }
5655             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5656                 message[1] = BOARD_RGHT   - 1 - j + '1';
5657                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5658             }
5659             SendToProgram(message, cps);
5660           }
5661         }
5662       }
5663
5664       SendToProgram(".\n", cps);
5665     }
5666     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5667 }
5668
5669 static int autoQueen; // [HGM] oneclick
5670
5671 int
5672 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5673 {
5674     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5675     /* [HGM] add Shogi promotions */
5676     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5677     ChessSquare piece;
5678     ChessMove moveType;
5679     Boolean premove;
5680
5681     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5682     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5683
5684     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5685       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5686         return FALSE;
5687
5688     piece = boards[currentMove][fromY][fromX];
5689     if(gameInfo.variant == VariantShogi) {
5690         promotionZoneSize = BOARD_HEIGHT/3;
5691         highestPromotingPiece = (int)WhiteFerz;
5692     } else if(gameInfo.variant == VariantMakruk) {
5693         promotionZoneSize = 3;
5694     }
5695
5696     // Treat Lance as Pawn when it is not representing Amazon
5697     if(gameInfo.variant != VariantSuper) {
5698         if(piece == WhiteLance) piece = WhitePawn; else
5699         if(piece == BlackLance) piece = BlackPawn;
5700     }
5701
5702     // next weed out all moves that do not touch the promotion zone at all
5703     if((int)piece >= BlackPawn) {
5704         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5705              return FALSE;
5706         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5707     } else {
5708         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5709            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5710     }
5711
5712     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5713
5714     // weed out mandatory Shogi promotions
5715     if(gameInfo.variant == VariantShogi) {
5716         if(piece >= BlackPawn) {
5717             if(toY == 0 && piece == BlackPawn ||
5718                toY == 0 && piece == BlackQueen ||
5719                toY <= 1 && piece == BlackKnight) {
5720                 *promoChoice = '+';
5721                 return FALSE;
5722             }
5723         } else {
5724             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5725                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5726                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5727                 *promoChoice = '+';
5728                 return FALSE;
5729             }
5730         }
5731     }
5732
5733     // weed out obviously illegal Pawn moves
5734     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5735         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5736         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5737         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5738         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5739         // note we are not allowed to test for valid (non-)capture, due to premove
5740     }
5741
5742     // we either have a choice what to promote to, or (in Shogi) whether to promote
5743     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5744         *promoChoice = PieceToChar(BlackFerz);  // no choice
5745         return FALSE;
5746     }
5747     // no sense asking what we must promote to if it is going to explode...
5748     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5749         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5750         return FALSE;
5751     }
5752     // give caller the default choice even if we will not make it
5753     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5754          *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5755     else *promoChoice = PieceToChar(BlackQueen);
5756     if(autoQueen) return FALSE; // predetermined
5757
5758     // suppress promotion popup on illegal moves that are not premoves
5759     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5760               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5761     if(appData.testLegality && !premove) {
5762         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5763                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5764         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5765             return FALSE;
5766     }
5767
5768     return TRUE;
5769 }
5770
5771 int
5772 InPalace(row, column)
5773      int row, column;
5774 {   /* [HGM] for Xiangqi */
5775     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5776          column < (BOARD_WIDTH + 4)/2 &&
5777          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5778     return FALSE;
5779 }
5780
5781 int
5782 PieceForSquare (x, y)
5783      int x;
5784      int y;
5785 {
5786   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5787      return -1;
5788   else
5789      return boards[currentMove][y][x];
5790 }
5791
5792 int
5793 OKToStartUserMove(x, y)
5794      int x, y;
5795 {
5796     ChessSquare from_piece;
5797     int white_piece;
5798
5799     if (matchMode) return FALSE;
5800     if (gameMode == EditPosition) return TRUE;
5801
5802     if (x >= 0 && y >= 0)
5803       from_piece = boards[currentMove][y][x];
5804     else
5805       from_piece = EmptySquare;
5806
5807     if (from_piece == EmptySquare) return FALSE;
5808
5809     white_piece = (int)from_piece >= (int)WhitePawn &&
5810       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5811
5812     switch (gameMode) {
5813       case PlayFromGameFile:
5814       case AnalyzeFile:
5815       case TwoMachinesPlay:
5816       case EndOfGame:
5817         return FALSE;
5818
5819       case IcsObserving:
5820       case IcsIdle:
5821         return FALSE;
5822
5823       case MachinePlaysWhite:
5824       case IcsPlayingBlack:
5825         if (appData.zippyPlay) return FALSE;
5826         if (white_piece) {
5827             DisplayMoveError(_("You are playing Black"));
5828             return FALSE;
5829         }
5830         break;
5831
5832       case MachinePlaysBlack:
5833       case IcsPlayingWhite:
5834         if (appData.zippyPlay) return FALSE;
5835         if (!white_piece) {
5836             DisplayMoveError(_("You are playing White"));
5837             return FALSE;
5838         }
5839         break;
5840
5841       case EditGame:
5842         if (!white_piece && WhiteOnMove(currentMove)) {
5843             DisplayMoveError(_("It is White's turn"));
5844             return FALSE;
5845         }
5846         if (white_piece && !WhiteOnMove(currentMove)) {
5847             DisplayMoveError(_("It is Black's turn"));
5848             return FALSE;
5849         }
5850         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5851             /* Editing correspondence game history */
5852             /* Could disallow this or prompt for confirmation */
5853             cmailOldMove = -1;
5854         }
5855         break;
5856
5857       case BeginningOfGame:
5858         if (appData.icsActive) return FALSE;
5859         if (!appData.noChessProgram) {
5860             if (!white_piece) {
5861                 DisplayMoveError(_("You are playing White"));
5862                 return FALSE;
5863             }
5864         }
5865         break;
5866
5867       case Training:
5868         if (!white_piece && WhiteOnMove(currentMove)) {
5869             DisplayMoveError(_("It is White's turn"));
5870             return FALSE;
5871         }
5872         if (white_piece && !WhiteOnMove(currentMove)) {
5873             DisplayMoveError(_("It is Black's turn"));
5874             return FALSE;
5875         }
5876         break;
5877
5878       default:
5879       case IcsExamining:
5880         break;
5881     }
5882     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5883         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5884         && gameMode != AnalyzeFile && gameMode != Training) {
5885         DisplayMoveError(_("Displayed position is not current"));
5886         return FALSE;
5887     }
5888     return TRUE;
5889 }
5890
5891 Boolean
5892 OnlyMove(int *x, int *y, Boolean captures) {
5893     DisambiguateClosure cl;
5894     if (appData.zippyPlay) return FALSE;
5895     switch(gameMode) {
5896       case MachinePlaysBlack:
5897       case IcsPlayingWhite:
5898       case BeginningOfGame:
5899         if(!WhiteOnMove(currentMove)) return FALSE;
5900         break;
5901       case MachinePlaysWhite:
5902       case IcsPlayingBlack:
5903         if(WhiteOnMove(currentMove)) return FALSE;
5904         break;
5905       case EditGame:
5906         break;
5907       default:
5908         return FALSE;
5909     }
5910     cl.pieceIn = EmptySquare;
5911     cl.rfIn = *y;
5912     cl.ffIn = *x;
5913     cl.rtIn = -1;
5914     cl.ftIn = -1;
5915     cl.promoCharIn = NULLCHAR;
5916     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5917     if( cl.kind == NormalMove ||
5918         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5919         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5920         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5921       fromX = cl.ff;
5922       fromY = cl.rf;
5923       *x = cl.ft;
5924       *y = cl.rt;
5925       return TRUE;
5926     }
5927     if(cl.kind != ImpossibleMove) return FALSE;
5928     cl.pieceIn = EmptySquare;
5929     cl.rfIn = -1;
5930     cl.ffIn = -1;
5931     cl.rtIn = *y;
5932     cl.ftIn = *x;
5933     cl.promoCharIn = NULLCHAR;
5934     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5935     if( cl.kind == NormalMove ||
5936         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5937         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5938         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5939       fromX = cl.ff;
5940       fromY = cl.rf;
5941       *x = cl.ft;
5942       *y = cl.rt;
5943       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5944       return TRUE;
5945     }
5946     return FALSE;
5947 }
5948
5949 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5950 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5951 int lastLoadGameUseList = FALSE;
5952 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5953 ChessMove lastLoadGameStart = EndOfFile;
5954
5955 void
5956 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5957      int fromX, fromY, toX, toY;
5958      int promoChar;
5959 {
5960     ChessMove moveType;
5961     ChessSquare pdown, pup;
5962
5963     /* Check if the user is playing in turn.  This is complicated because we
5964        let the user "pick up" a piece before it is his turn.  So the piece he
5965        tried to pick up may have been captured by the time he puts it down!
5966        Therefore we use the color the user is supposed to be playing in this
5967        test, not the color of the piece that is currently on the starting
5968        square---except in EditGame mode, where the user is playing both
5969        sides; fortunately there the capture race can't happen.  (It can
5970        now happen in IcsExamining mode, but that's just too bad.  The user
5971        will get a somewhat confusing message in that case.)
5972        */
5973
5974     switch (gameMode) {
5975       case PlayFromGameFile:
5976       case AnalyzeFile:
5977       case TwoMachinesPlay:
5978       case EndOfGame:
5979       case IcsObserving:
5980       case IcsIdle:
5981         /* We switched into a game mode where moves are not accepted,
5982            perhaps while the mouse button was down. */
5983         return;
5984
5985       case MachinePlaysWhite:
5986         /* User is moving for Black */
5987         if (WhiteOnMove(currentMove)) {
5988             DisplayMoveError(_("It is White's turn"));
5989             return;
5990         }
5991         break;
5992
5993       case MachinePlaysBlack:
5994         /* User is moving for White */
5995         if (!WhiteOnMove(currentMove)) {
5996             DisplayMoveError(_("It is Black's turn"));
5997             return;
5998         }
5999         break;
6000
6001       case EditGame:
6002       case IcsExamining:
6003       case BeginningOfGame:
6004       case AnalyzeMode:
6005       case Training:
6006         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6007         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6008             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6009             /* User is moving for Black */
6010             if (WhiteOnMove(currentMove)) {
6011                 DisplayMoveError(_("It is White's turn"));
6012                 return;
6013             }
6014         } else {
6015             /* User is moving for White */
6016             if (!WhiteOnMove(currentMove)) {
6017                 DisplayMoveError(_("It is Black's turn"));
6018                 return;
6019             }
6020         }
6021         break;
6022
6023       case IcsPlayingBlack:
6024         /* User is moving for Black */
6025         if (WhiteOnMove(currentMove)) {
6026             if (!appData.premove) {
6027                 DisplayMoveError(_("It is White's turn"));
6028             } else if (toX >= 0 && toY >= 0) {
6029                 premoveToX = toX;
6030                 premoveToY = toY;
6031                 premoveFromX = fromX;
6032                 premoveFromY = fromY;
6033                 premovePromoChar = promoChar;
6034                 gotPremove = 1;
6035                 if (appData.debugMode)
6036                     fprintf(debugFP, "Got premove: fromX %d,"
6037                             "fromY %d, toX %d, toY %d\n",
6038                             fromX, fromY, toX, toY);
6039             }
6040             return;
6041         }
6042         break;
6043
6044       case IcsPlayingWhite:
6045         /* User is moving for White */
6046         if (!WhiteOnMove(currentMove)) {
6047             if (!appData.premove) {
6048                 DisplayMoveError(_("It is Black's turn"));
6049             } else if (toX >= 0 && toY >= 0) {
6050                 premoveToX = toX;
6051                 premoveToY = toY;
6052                 premoveFromX = fromX;
6053                 premoveFromY = fromY;
6054                 premovePromoChar = promoChar;
6055                 gotPremove = 1;
6056                 if (appData.debugMode)
6057                     fprintf(debugFP, "Got premove: fromX %d,"
6058                             "fromY %d, toX %d, toY %d\n",
6059                             fromX, fromY, toX, toY);
6060             }
6061             return;
6062         }
6063         break;
6064
6065       default:
6066         break;
6067
6068       case EditPosition:
6069         /* EditPosition, empty square, or different color piece;
6070            click-click move is possible */
6071         if (toX == -2 || toY == -2) {
6072             boards[0][fromY][fromX] = EmptySquare;
6073             DrawPosition(FALSE, boards[currentMove]);
6074             return;
6075         } else if (toX >= 0 && toY >= 0) {
6076             boards[0][toY][toX] = boards[0][fromY][fromX];
6077             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6078                 if(boards[0][fromY][0] != EmptySquare) {
6079                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6080                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6081                 }
6082             } else
6083             if(fromX == BOARD_RGHT+1) {
6084                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6085                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6086                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6087                 }
6088             } else
6089             boards[0][fromY][fromX] = EmptySquare;
6090             DrawPosition(FALSE, boards[currentMove]);
6091             return;
6092         }
6093         return;
6094     }
6095
6096     if(toX < 0 || toY < 0) return;
6097     pdown = boards[currentMove][fromY][fromX];
6098     pup = boards[currentMove][toY][toX];
6099
6100     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6101     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6102          if( pup != EmptySquare ) return;
6103          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6104            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6105                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6106            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6107            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6108            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6109            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6110          fromY = DROP_RANK;
6111     }
6112
6113     /* [HGM] always test for legality, to get promotion info */
6114     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6115                                          fromY, fromX, toY, toX, promoChar);
6116     /* [HGM] but possibly ignore an IllegalMove result */
6117     if (appData.testLegality) {
6118         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6119             DisplayMoveError(_("Illegal move"));
6120             return;
6121         }
6122     }
6123
6124     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6125 }
6126
6127 /* Common tail of UserMoveEvent and DropMenuEvent */
6128 int
6129 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6130      ChessMove moveType;
6131      int fromX, fromY, toX, toY;
6132      /*char*/int promoChar;
6133 {
6134     char *bookHit = 0;
6135
6136     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6137         // [HGM] superchess: suppress promotions to non-available piece
6138         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6139         if(WhiteOnMove(currentMove)) {
6140             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6141         } else {
6142             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6143         }
6144     }
6145
6146     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6147        move type in caller when we know the move is a legal promotion */
6148     if(moveType == NormalMove && promoChar)
6149         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6150
6151     /* [HGM] <popupFix> The following if has been moved here from
6152        UserMoveEvent(). Because it seemed to belong here (why not allow
6153        piece drops in training games?), and because it can only be
6154        performed after it is known to what we promote. */
6155     if (gameMode == Training) {
6156       /* compare the move played on the board to the next move in the
6157        * game. If they match, display the move and the opponent's response.
6158        * If they don't match, display an error message.
6159        */
6160       int saveAnimate;
6161       Board testBoard;
6162       CopyBoard(testBoard, boards[currentMove]);
6163       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6164
6165       if (CompareBoards(testBoard, boards[currentMove+1])) {
6166         ForwardInner(currentMove+1);
6167
6168         /* Autoplay the opponent's response.
6169          * if appData.animate was TRUE when Training mode was entered,
6170          * the response will be animated.
6171          */
6172         saveAnimate = appData.animate;
6173         appData.animate = animateTraining;
6174         ForwardInner(currentMove+1);
6175         appData.animate = saveAnimate;
6176
6177         /* check for the end of the game */
6178         if (currentMove >= forwardMostMove) {
6179           gameMode = PlayFromGameFile;
6180           ModeHighlight();
6181           SetTrainingModeOff();
6182           DisplayInformation(_("End of game"));
6183         }
6184       } else {
6185         DisplayError(_("Incorrect move"), 0);
6186       }
6187       return 1;
6188     }
6189
6190   /* Ok, now we know that the move is good, so we can kill
6191      the previous line in Analysis Mode */
6192   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6193                                 && currentMove < forwardMostMove) {
6194     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6195     else forwardMostMove = currentMove;
6196   }
6197
6198   /* If we need the chess program but it's dead, restart it */
6199   ResurrectChessProgram();
6200
6201   /* A user move restarts a paused game*/
6202   if (pausing)
6203     PauseEvent();
6204
6205   thinkOutput[0] = NULLCHAR;
6206
6207   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6208
6209   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6210     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6211     return 1;
6212   }
6213
6214   if (gameMode == BeginningOfGame) {
6215     if (appData.noChessProgram) {
6216       gameMode = EditGame;
6217       SetGameInfo();
6218     } else {
6219       char buf[MSG_SIZ];
6220       gameMode = MachinePlaysBlack;
6221       StartClocks();
6222       SetGameInfo();
6223       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6224       DisplayTitle(buf);
6225       if (first.sendName) {
6226         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6227         SendToProgram(buf, &first);
6228       }
6229       StartClocks();
6230     }
6231     ModeHighlight();
6232   }
6233
6234   /* Relay move to ICS or chess engine */
6235   if (appData.icsActive) {
6236     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6237         gameMode == IcsExamining) {
6238       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6239         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6240         SendToICS("draw ");
6241         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6242       }
6243       // also send plain move, in case ICS does not understand atomic claims
6244       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6245       ics_user_moved = 1;
6246     }
6247   } else {
6248     if (first.sendTime && (gameMode == BeginningOfGame ||
6249                            gameMode == MachinePlaysWhite ||
6250                            gameMode == MachinePlaysBlack)) {
6251       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6252     }
6253     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6254          // [HGM] book: if program might be playing, let it use book
6255         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6256         first.maybeThinking = TRUE;
6257     } else SendMoveToProgram(forwardMostMove-1, &first);
6258     if (currentMove == cmailOldMove + 1) {
6259       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6260     }
6261   }
6262
6263   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6264
6265   switch (gameMode) {
6266   case EditGame:
6267     if(appData.testLegality)
6268     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6269     case MT_NONE:
6270     case MT_CHECK:
6271       break;
6272     case MT_CHECKMATE:
6273     case MT_STAINMATE:
6274       if (WhiteOnMove(currentMove)) {
6275         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6276       } else {
6277         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6278       }
6279       break;
6280     case MT_STALEMATE:
6281       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6282       break;
6283     }
6284     break;
6285
6286   case MachinePlaysBlack:
6287   case MachinePlaysWhite:
6288     /* disable certain menu options while machine is thinking */
6289     SetMachineThinkingEnables();
6290     break;
6291
6292   default:
6293     break;
6294   }
6295
6296   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6297
6298   if(bookHit) { // [HGM] book: simulate book reply
6299         static char bookMove[MSG_SIZ]; // a bit generous?
6300
6301         programStats.nodes = programStats.depth = programStats.time =
6302         programStats.score = programStats.got_only_move = 0;
6303         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6304
6305         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6306         strcat(bookMove, bookHit);
6307         HandleMachineMove(bookMove, &first);
6308   }
6309   return 1;
6310 }
6311
6312 void
6313 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6314      Board board;
6315      int flags;
6316      ChessMove kind;
6317      int rf, ff, rt, ft;
6318      VOIDSTAR closure;
6319 {
6320     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6321     Markers *m = (Markers *) closure;
6322     if(rf == fromY && ff == fromX)
6323         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6324                          || kind == WhiteCapturesEnPassant
6325                          || kind == BlackCapturesEnPassant);
6326     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6327 }
6328
6329 void
6330 MarkTargetSquares(int clear)
6331 {
6332   int x, y;
6333   if(!appData.markers || !appData.highlightDragging ||
6334      !appData.testLegality || gameMode == EditPosition) return;
6335   if(clear) {
6336     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6337   } else {
6338     int capt = 0;
6339     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6340     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6341       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6342       if(capt)
6343       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6344     }
6345   }
6346   DrawPosition(TRUE, NULL);
6347 }
6348
6349 int
6350 Explode(Board board, int fromX, int fromY, int toX, int toY)
6351 {
6352     if(gameInfo.variant == VariantAtomic &&
6353        (board[toY][toX] != EmptySquare ||                     // capture?
6354         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6355                          board[fromY][fromX] == BlackPawn   )
6356       )) {
6357         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6358         return TRUE;
6359     }
6360     return FALSE;
6361 }
6362
6363 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6364
6365 void LeftClick(ClickType clickType, int xPix, int yPix)
6366 {
6367     int x, y;
6368     Boolean saveAnimate;
6369     static int second = 0, promotionChoice = 0, dragging = 0;
6370     char promoChoice = NULLCHAR;
6371
6372     if(appData.seekGraph && appData.icsActive && loggedOn &&
6373         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6374         SeekGraphClick(clickType, xPix, yPix, 0);
6375         return;
6376     }
6377
6378     if (clickType == Press) ErrorPopDown();
6379     MarkTargetSquares(1);
6380
6381     x = EventToSquare(xPix, BOARD_WIDTH);
6382     y = EventToSquare(yPix, BOARD_HEIGHT);
6383     if (!flipView && y >= 0) {
6384         y = BOARD_HEIGHT - 1 - y;
6385     }
6386     if (flipView && x >= 0) {
6387         x = BOARD_WIDTH - 1 - x;
6388     }
6389
6390     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6391         if(clickType == Release) return; // ignore upclick of click-click destination
6392         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6393         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6394         if(gameInfo.holdingsWidth &&
6395                 (WhiteOnMove(currentMove)
6396                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6397                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6398             // click in right holdings, for determining promotion piece
6399             ChessSquare p = boards[currentMove][y][x];
6400             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6401             if(p != EmptySquare) {
6402                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6403                 fromX = fromY = -1;
6404                 return;
6405             }
6406         }
6407         DrawPosition(FALSE, boards[currentMove]);
6408         return;
6409     }
6410
6411     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6412     if(clickType == Press
6413             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6414               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6415               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6416         return;
6417
6418     autoQueen = appData.alwaysPromoteToQueen;
6419
6420     if (fromX == -1) {
6421       gatingPiece = EmptySquare;
6422       if (clickType != Press) {
6423         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6424             DragPieceEnd(xPix, yPix); dragging = 0;
6425             DrawPosition(FALSE, NULL);
6426         }
6427         return;
6428       }
6429       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6430             /* First square */
6431             if (OKToStartUserMove(x, y)) {
6432                 fromX = x;
6433                 fromY = y;
6434                 second = 0;
6435                 MarkTargetSquares(0);
6436                 DragPieceBegin(xPix, yPix); dragging = 1;
6437                 if (appData.highlightDragging) {
6438                     SetHighlights(x, y, -1, -1);
6439                 }
6440             }
6441             return;
6442         }
6443     }
6444
6445     /* fromX != -1 */
6446     if (clickType == Press && gameMode != EditPosition) {
6447         ChessSquare fromP;
6448         ChessSquare toP;
6449         int frc;
6450
6451         // ignore off-board to clicks
6452         if(y < 0 || x < 0) return;
6453
6454         /* Check if clicking again on the same color piece */
6455         fromP = boards[currentMove][fromY][fromX];
6456         toP = boards[currentMove][y][x];
6457         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6458         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6459              WhitePawn <= toP && toP <= WhiteKing &&
6460              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6461              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6462             (BlackPawn <= fromP && fromP <= BlackKing &&
6463              BlackPawn <= toP && toP <= BlackKing &&
6464              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6465              !(fromP == BlackKing && toP == BlackRook && frc))) {
6466             /* Clicked again on same color piece -- changed his mind */
6467             second = (x == fromX && y == fromY);
6468            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6469             if (appData.highlightDragging) {
6470                 SetHighlights(x, y, -1, -1);
6471             } else {
6472                 ClearHighlights();
6473             }
6474             if (OKToStartUserMove(x, y)) {
6475                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6476                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6477                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6478                  gatingPiece = boards[currentMove][fromY][fromX];
6479                 else gatingPiece = EmptySquare;
6480                 fromX = x;
6481                 fromY = y; dragging = 1;
6482                 MarkTargetSquares(0);
6483                 DragPieceBegin(xPix, yPix);
6484             }
6485            }
6486            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6487            second = FALSE; 
6488         }
6489         // ignore clicks on holdings
6490         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6491     }
6492
6493     if (clickType == Release && x == fromX && y == fromY) {
6494         DragPieceEnd(xPix, yPix); dragging = 0;
6495         if (appData.animateDragging) {
6496             /* Undo animation damage if any */
6497             DrawPosition(FALSE, NULL);
6498         }
6499         if (second) {
6500             /* Second up/down in same square; just abort move */
6501             second = 0;
6502             fromX = fromY = -1;
6503             gatingPiece = EmptySquare;
6504             ClearHighlights();
6505             gotPremove = 0;
6506             ClearPremoveHighlights();
6507         } else {
6508             /* First upclick in same square; start click-click mode */
6509             SetHighlights(x, y, -1, -1);
6510         }
6511         return;
6512     }
6513
6514     /* we now have a different from- and (possibly off-board) to-square */
6515     /* Completed move */
6516     toX = x;
6517     toY = y;
6518     saveAnimate = appData.animate;
6519     if (clickType == Press) {
6520         /* Finish clickclick move */
6521         if (appData.animate || appData.highlightLastMove) {
6522             SetHighlights(fromX, fromY, toX, toY);
6523         } else {
6524             ClearHighlights();
6525         }
6526     } else {
6527         /* Finish drag move */
6528         if (appData.highlightLastMove) {
6529             SetHighlights(fromX, fromY, toX, toY);
6530         } else {
6531             ClearHighlights();
6532         }
6533         DragPieceEnd(xPix, yPix); dragging = 0;
6534         /* Don't animate move and drag both */
6535         appData.animate = FALSE;
6536     }
6537
6538     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6539     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6540         ChessSquare piece = boards[currentMove][fromY][fromX];
6541         if(gameMode == EditPosition && piece != EmptySquare &&
6542            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6543             int n;
6544
6545             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6546                 n = PieceToNumber(piece - (int)BlackPawn);
6547                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6548                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6549                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6550             } else
6551             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6552                 n = PieceToNumber(piece);
6553                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6554                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6555                 boards[currentMove][n][BOARD_WIDTH-2]++;
6556             }
6557             boards[currentMove][fromY][fromX] = EmptySquare;
6558         }
6559         ClearHighlights();
6560         fromX = fromY = -1;
6561         DrawPosition(TRUE, boards[currentMove]);
6562         return;
6563     }
6564
6565     // off-board moves should not be highlighted
6566     if(x < 0 || y < 0) ClearHighlights();
6567
6568     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6569
6570     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6571         SetHighlights(fromX, fromY, toX, toY);
6572         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6573             // [HGM] super: promotion to captured piece selected from holdings
6574             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6575             promotionChoice = TRUE;
6576             // kludge follows to temporarily execute move on display, without promoting yet
6577             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6578             boards[currentMove][toY][toX] = p;
6579             DrawPosition(FALSE, boards[currentMove]);
6580             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6581             boards[currentMove][toY][toX] = q;
6582             DisplayMessage("Click in holdings to choose piece", "");
6583             return;
6584         }
6585         PromotionPopUp();
6586     } else {
6587         int oldMove = currentMove;
6588         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6589         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6590         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6591         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6592            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6593             DrawPosition(TRUE, boards[currentMove]);
6594         fromX = fromY = -1;
6595     }
6596     appData.animate = saveAnimate;
6597     if (appData.animate || appData.animateDragging) {
6598         /* Undo animation damage if needed */
6599         DrawPosition(FALSE, NULL);
6600     }
6601 }
6602
6603 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6604 {   // front-end-free part taken out of PieceMenuPopup
6605     int whichMenu; int xSqr, ySqr;
6606
6607     if(seekGraphUp) { // [HGM] seekgraph
6608         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6609         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6610         return -2;
6611     }
6612
6613     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6614          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6615         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6616         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6617         if(action == Press)   {
6618             originalFlip = flipView;
6619             flipView = !flipView; // temporarily flip board to see game from partners perspective
6620             DrawPosition(TRUE, partnerBoard);
6621             DisplayMessage(partnerStatus, "");
6622             partnerUp = TRUE;
6623         } else if(action == Release) {
6624             flipView = originalFlip;
6625             DrawPosition(TRUE, boards[currentMove]);
6626             partnerUp = FALSE;
6627         }
6628         return -2;
6629     }
6630
6631     xSqr = EventToSquare(x, BOARD_WIDTH);
6632     ySqr = EventToSquare(y, BOARD_HEIGHT);
6633     if (action == Release) {
6634         if(pieceSweep != EmptySquare) {
6635             EditPositionMenuEvent(pieceSweep, toX, toY);
6636             pieceSweep = EmptySquare;
6637         } else UnLoadPV(); // [HGM] pv
6638     }
6639     if (action != Press) return -2; // return code to be ignored
6640     switch (gameMode) {
6641       case IcsExamining:
6642         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6643       case EditPosition:
6644         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6645         if (xSqr < 0 || ySqr < 0) return -1;
6646         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6647         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6648         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6649         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6650         NextPiece(0);
6651         return -2;\r
6652       case IcsObserving:
6653         if(!appData.icsEngineAnalyze) return -1;
6654       case IcsPlayingWhite:
6655       case IcsPlayingBlack:
6656         if(!appData.zippyPlay) goto noZip;
6657       case AnalyzeMode:
6658       case AnalyzeFile:
6659       case MachinePlaysWhite:
6660       case MachinePlaysBlack:
6661       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6662         if (!appData.dropMenu) {
6663           LoadPV(x, y);
6664           return 2; // flag front-end to grab mouse events
6665         }
6666         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6667            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6668       case EditGame:
6669       noZip:
6670         if (xSqr < 0 || ySqr < 0) return -1;
6671         if (!appData.dropMenu || appData.testLegality &&
6672             gameInfo.variant != VariantBughouse &&
6673             gameInfo.variant != VariantCrazyhouse) return -1;
6674         whichMenu = 1; // drop menu
6675         break;
6676       default:
6677         return -1;
6678     }
6679
6680     if (((*fromX = xSqr) < 0) ||
6681         ((*fromY = ySqr) < 0)) {
6682         *fromX = *fromY = -1;
6683         return -1;
6684     }
6685     if (flipView)
6686       *fromX = BOARD_WIDTH - 1 - *fromX;
6687     else
6688       *fromY = BOARD_HEIGHT - 1 - *fromY;
6689
6690     return whichMenu;
6691 }
6692
6693 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6694 {
6695 //    char * hint = lastHint;
6696     FrontEndProgramStats stats;
6697
6698     stats.which = cps == &first ? 0 : 1;
6699     stats.depth = cpstats->depth;
6700     stats.nodes = cpstats->nodes;
6701     stats.score = cpstats->score;
6702     stats.time = cpstats->time;
6703     stats.pv = cpstats->movelist;
6704     stats.hint = lastHint;
6705     stats.an_move_index = 0;
6706     stats.an_move_count = 0;
6707
6708     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6709         stats.hint = cpstats->move_name;
6710         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6711         stats.an_move_count = cpstats->nr_moves;
6712     }
6713
6714     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
6715
6716     SetProgramStats( &stats );
6717 }
6718
6719 void
6720 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6721 {       // count all piece types
6722         int p, f, r;
6723         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6724         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6725         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6726                 p = board[r][f];
6727                 pCnt[p]++;
6728                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6729                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6730                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6731                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6732                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6733                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6734         }
6735 }
6736
6737 int
6738 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6739 {
6740         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6741         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6742
6743         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6744         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6745         if(myPawns == 2 && nMine == 3) // KPP
6746             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6747         if(myPawns == 1 && nMine == 2) // KP
6748             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6749         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6750             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6751         if(myPawns) return FALSE;
6752         if(pCnt[WhiteRook+side])
6753             return pCnt[BlackRook-side] ||
6754                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6755                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6756                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6757         if(pCnt[WhiteCannon+side]) {
6758             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6759             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6760         }
6761         if(pCnt[WhiteKnight+side])
6762             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6763         return FALSE;
6764 }
6765
6766 int
6767 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6768 {
6769         VariantClass v = gameInfo.variant;
6770
6771         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6772         if(v == VariantShatranj) return TRUE; // always winnable through baring
6773         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6774         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6775
6776         if(v == VariantXiangqi) {
6777                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6778
6779                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6780                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6781                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6782                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6783                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6784                 if(stale) // we have at least one last-rank P plus perhaps C
6785                     return majors // KPKX
6786                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6787                 else // KCA*E*
6788                     return pCnt[WhiteFerz+side] // KCAK
6789                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6790                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6791                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6792
6793         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6794                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6795
6796                 if(nMine == 1) return FALSE; // bare King
6797                 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
6798                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6799                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6800                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6801                 if(pCnt[WhiteKnight+side])
6802                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6803                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6804                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6805                 if(nBishops)
6806                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6807                 if(pCnt[WhiteAlfil+side])
6808                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6809                 if(pCnt[WhiteWazir+side])
6810                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6811         }
6812
6813         return TRUE;
6814 }
6815
6816 int
6817 Adjudicate(ChessProgramState *cps)
6818 {       // [HGM] some adjudications useful with buggy engines
6819         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6820         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6821         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6822         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6823         int k, count = 0; static int bare = 1;
6824         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6825         Boolean canAdjudicate = !appData.icsActive;
6826
6827         // most tests only when we understand the game, i.e. legality-checking on
6828             if( appData.testLegality )
6829             {   /* [HGM] Some more adjudications for obstinate engines */
6830                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6831                 static int moveCount = 6;
6832                 ChessMove result;
6833                 char *reason = NULL;
6834
6835                 /* Count what is on board. */
6836                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6837
6838                 /* Some material-based adjudications that have to be made before stalemate test */
6839                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6840                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6841                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6842                      if(canAdjudicate && appData.checkMates) {
6843                          if(engineOpponent)
6844                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6845                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6846                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6847                          return 1;
6848                      }
6849                 }
6850
6851                 /* Bare King in Shatranj (loses) or Losers (wins) */
6852                 if( nrW == 1 || nrB == 1) {
6853                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6854                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6855                      if(canAdjudicate && appData.checkMates) {
6856                          if(engineOpponent)
6857                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6858                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6859                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6860                          return 1;
6861                      }
6862                   } else
6863                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6864                   {    /* bare King */
6865                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6866                         if(canAdjudicate && appData.checkMates) {
6867                             /* but only adjudicate if adjudication enabled */
6868                             if(engineOpponent)
6869                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6870                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6871                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6872                             return 1;
6873                         }
6874                   }
6875                 } else bare = 1;
6876
6877
6878             // don't wait for engine to announce game end if we can judge ourselves
6879             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6880               case MT_CHECK:
6881                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6882                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6883                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6884                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6885                             checkCnt++;
6886                         if(checkCnt >= 2) {
6887                             reason = "Xboard adjudication: 3rd check";
6888                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6889                             break;
6890                         }
6891                     }
6892                 }
6893               case MT_NONE:
6894               default:
6895                 break;
6896               case MT_STALEMATE:
6897               case MT_STAINMATE:
6898                 reason = "Xboard adjudication: Stalemate";
6899                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6900                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6901                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6902                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6903                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6904                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6905                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6906                                                                         EP_CHECKMATE : EP_WINS);
6907                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6908                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6909                 }
6910                 break;
6911               case MT_CHECKMATE:
6912                 reason = "Xboard adjudication: Checkmate";
6913                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6914                 break;
6915             }
6916
6917                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6918                     case EP_STALEMATE:
6919                         result = GameIsDrawn; break;
6920                     case EP_CHECKMATE:
6921                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6922                     case EP_WINS:
6923                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6924                     default:
6925                         result = EndOfFile;
6926                 }
6927                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6928                     if(engineOpponent)
6929                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6930                     GameEnds( result, reason, GE_XBOARD );
6931                     return 1;
6932                 }
6933
6934                 /* Next absolutely insufficient mating material. */
6935                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6936                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6937                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6938
6939                      /* always flag draws, for judging claims */
6940                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6941
6942                      if(canAdjudicate && appData.materialDraws) {
6943                          /* but only adjudicate them if adjudication enabled */
6944                          if(engineOpponent) {
6945                            SendToProgram("force\n", engineOpponent); // suppress reply
6946                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6947                          }
6948                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6949                          return 1;
6950                      }
6951                 }
6952
6953                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6954                 if(gameInfo.variant == VariantXiangqi ?
6955                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6956                  : nrW + nrB == 4 &&
6957                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6958                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6959                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6960                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6961                    ) ) {
6962                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6963                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6964                           if(engineOpponent) {
6965                             SendToProgram("force\n", engineOpponent); // suppress reply
6966                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6967                           }
6968                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6969                           return 1;
6970                      }
6971                 } else moveCount = 6;
6972             }
6973         if (appData.debugMode) { int i;
6974             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6975                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6976                     appData.drawRepeats);
6977             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6978               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6979
6980         }
6981
6982         // Repetition draws and 50-move rule can be applied independently of legality testing
6983
6984                 /* Check for rep-draws */
6985                 count = 0;
6986                 for(k = forwardMostMove-2;
6987                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6988                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6989                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6990                     k-=2)
6991                 {   int rights=0;
6992                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6993                         /* compare castling rights */
6994                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6995                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6996                                 rights++; /* King lost rights, while rook still had them */
6997                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6998                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6999                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7000                                    rights++; /* but at least one rook lost them */
7001                         }
7002                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7003                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7004                                 rights++;
7005                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7006                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7007                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7008                                    rights++;
7009                         }
7010                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7011                             && appData.drawRepeats > 1) {
7012                              /* adjudicate after user-specified nr of repeats */
7013                              int result = GameIsDrawn;
7014                              char *details = "XBoard adjudication: repetition draw";
7015                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7016                                 // [HGM] xiangqi: check for forbidden perpetuals
7017                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7018                                 for(m=forwardMostMove; m>k; m-=2) {
7019                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7020                                         ourPerpetual = 0; // the current mover did not always check
7021                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7022                                         hisPerpetual = 0; // the opponent did not always check
7023                                 }
7024                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7025                                                                         ourPerpetual, hisPerpetual);
7026                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7027                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7028                                     details = "Xboard adjudication: perpetual checking";
7029                                 } else
7030                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7031                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7032                                 } else
7033                                 // Now check for perpetual chases
7034                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7035                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7036                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7037                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7038                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7039                                         details = "Xboard adjudication: perpetual chasing";
7040                                     } else
7041                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7042                                         break; // Abort repetition-checking loop.
7043                                 }
7044                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7045                              }
7046                              if(engineOpponent) {
7047                                SendToProgram("force\n", engineOpponent); // suppress reply
7048                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7049                              }
7050                              GameEnds( result, details, GE_XBOARD );
7051                              return 1;
7052                         }
7053                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7054                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7055                     }
7056                 }
7057
7058                 /* Now we test for 50-move draws. Determine ply count */
7059                 count = forwardMostMove;
7060                 /* look for last irreversble move */
7061                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7062                     count--;
7063                 /* if we hit starting position, add initial plies */
7064                 if( count == backwardMostMove )
7065                     count -= initialRulePlies;
7066                 count = forwardMostMove - count;
7067                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7068                         // adjust reversible move counter for checks in Xiangqi
7069                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7070                         if(i < backwardMostMove) i = backwardMostMove;
7071                         while(i <= forwardMostMove) {
7072                                 lastCheck = inCheck; // check evasion does not count
7073                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7074                                 if(inCheck || lastCheck) count--; // check does not count
7075                                 i++;
7076                         }
7077                 }
7078                 if( count >= 100)
7079                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7080                          /* this is used to judge if draw claims are legal */
7081                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7082                          if(engineOpponent) {
7083                            SendToProgram("force\n", engineOpponent); // suppress reply
7084                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7085                          }
7086                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7087                          return 1;
7088                 }
7089
7090                 /* if draw offer is pending, treat it as a draw claim
7091                  * when draw condition present, to allow engines a way to
7092                  * claim draws before making their move to avoid a race
7093                  * condition occurring after their move
7094                  */
7095                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7096                          char *p = NULL;
7097                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7098                              p = "Draw claim: 50-move rule";
7099                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7100                              p = "Draw claim: 3-fold repetition";
7101                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7102                              p = "Draw claim: insufficient mating material";
7103                          if( p != NULL && canAdjudicate) {
7104                              if(engineOpponent) {
7105                                SendToProgram("force\n", engineOpponent); // suppress reply
7106                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7107                              }
7108                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7109                              return 1;
7110                          }
7111                 }
7112
7113                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7114                     if(engineOpponent) {
7115                       SendToProgram("force\n", engineOpponent); // suppress reply
7116                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7117                     }
7118                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7119                     return 1;
7120                 }
7121         return 0;
7122 }
7123
7124 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7125 {   // [HGM] book: this routine intercepts moves to simulate book replies
7126     char *bookHit = NULL;
7127
7128     //first determine if the incoming move brings opponent into his book
7129     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7130         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7131     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7132     if(bookHit != NULL && !cps->bookSuspend) {
7133         // make sure opponent is not going to reply after receiving move to book position
7134         SendToProgram("force\n", cps);
7135         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7136     }
7137     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7138     // now arrange restart after book miss
7139     if(bookHit) {
7140         // after a book hit we never send 'go', and the code after the call to this routine
7141         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7142         char buf[MSG_SIZ];
7143         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7144         SendToProgram(buf, cps);
7145         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7146     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7147         SendToProgram("go\n", cps);
7148         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7149     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7150         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7151             SendToProgram("go\n", cps);
7152         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7153     }
7154     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7155 }
7156
7157 char *savedMessage;
7158 ChessProgramState *savedState;
7159 void DeferredBookMove(void)
7160 {
7161         if(savedState->lastPing != savedState->lastPong)
7162                     ScheduleDelayedEvent(DeferredBookMove, 10);
7163         else
7164         HandleMachineMove(savedMessage, savedState);
7165 }
7166
7167 void
7168 HandleMachineMove(message, cps)
7169      char *message;
7170      ChessProgramState *cps;
7171 {
7172     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7173     char realname[MSG_SIZ];
7174     int fromX, fromY, toX, toY;
7175     ChessMove moveType;
7176     char promoChar;
7177     char *p;
7178     int machineWhite;
7179     char *bookHit;
7180
7181     cps->userError = 0;
7182
7183 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7184     /*
7185      * Kludge to ignore BEL characters
7186      */
7187     while (*message == '\007') message++;
7188
7189     /*
7190      * [HGM] engine debug message: ignore lines starting with '#' character
7191      */
7192     if(cps->debug && *message == '#') return;
7193
7194     /*
7195      * Look for book output
7196      */
7197     if (cps == &first && bookRequested) {
7198         if (message[0] == '\t' || message[0] == ' ') {
7199             /* Part of the book output is here; append it */
7200             strcat(bookOutput, message);
7201             strcat(bookOutput, "  \n");
7202             return;
7203         } else if (bookOutput[0] != NULLCHAR) {
7204             /* All of book output has arrived; display it */
7205             char *p = bookOutput;
7206             while (*p != NULLCHAR) {
7207                 if (*p == '\t') *p = ' ';
7208                 p++;
7209             }
7210             DisplayInformation(bookOutput);
7211             bookRequested = FALSE;
7212             /* Fall through to parse the current output */
7213         }
7214     }
7215
7216     /*
7217      * Look for machine move.
7218      */
7219     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7220         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7221     {
7222         /* This method is only useful on engines that support ping */
7223         if (cps->lastPing != cps->lastPong) {
7224           if (gameMode == BeginningOfGame) {
7225             /* Extra move from before last new; ignore */
7226             if (appData.debugMode) {
7227                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7228             }
7229           } else {
7230             if (appData.debugMode) {
7231                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7232                         cps->which, gameMode);
7233             }
7234
7235             SendToProgram("undo\n", cps);
7236           }
7237           return;
7238         }
7239
7240         switch (gameMode) {
7241           case BeginningOfGame:
7242             /* Extra move from before last reset; ignore */
7243             if (appData.debugMode) {
7244                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7245             }
7246             return;
7247
7248           case EndOfGame:
7249           case IcsIdle:
7250           default:
7251             /* Extra move after we tried to stop.  The mode test is
7252                not a reliable way of detecting this problem, but it's
7253                the best we can do on engines that don't support ping.
7254             */
7255             if (appData.debugMode) {
7256                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7257                         cps->which, gameMode);
7258             }
7259             SendToProgram("undo\n", cps);
7260             return;
7261
7262           case MachinePlaysWhite:
7263           case IcsPlayingWhite:
7264             machineWhite = TRUE;
7265             break;
7266
7267           case MachinePlaysBlack:
7268           case IcsPlayingBlack:
7269             machineWhite = FALSE;
7270             break;
7271
7272           case TwoMachinesPlay:
7273             machineWhite = (cps->twoMachinesColor[0] == 'w');
7274             break;
7275         }
7276         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7277             if (appData.debugMode) {
7278                 fprintf(debugFP,
7279                         "Ignoring move out of turn by %s, gameMode %d"
7280                         ", forwardMost %d\n",
7281                         cps->which, gameMode, forwardMostMove);
7282             }
7283             return;
7284         }
7285
7286     if (appData.debugMode) { int f = forwardMostMove;
7287         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7288                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7289                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7290     }
7291         if(cps->alphaRank) AlphaRank(machineMove, 4);
7292         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7293                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7294             /* Machine move could not be parsed; ignore it. */
7295           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7296                     machineMove, _(cps->which));
7297             DisplayError(buf1, 0);
7298             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7299                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7300             if (gameMode == TwoMachinesPlay) {
7301               GameEnds(machineWhite ? BlackWins : WhiteWins,
7302                        buf1, GE_XBOARD);
7303             }
7304             return;
7305         }
7306
7307         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7308         /* So we have to redo legality test with true e.p. status here,  */
7309         /* to make sure an illegal e.p. capture does not slip through,   */
7310         /* to cause a forfeit on a justified illegal-move complaint      */
7311         /* of the opponent.                                              */
7312         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7313            ChessMove moveType;
7314            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7315                              fromY, fromX, toY, toX, promoChar);
7316             if (appData.debugMode) {
7317                 int i;
7318                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7319                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7320                 fprintf(debugFP, "castling rights\n");
7321             }
7322             if(moveType == IllegalMove) {
7323               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7324                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7325                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7326                            buf1, GE_XBOARD);
7327                 return;
7328            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7329            /* [HGM] Kludge to handle engines that send FRC-style castling
7330               when they shouldn't (like TSCP-Gothic) */
7331            switch(moveType) {
7332              case WhiteASideCastleFR:
7333              case BlackASideCastleFR:
7334                toX+=2;
7335                currentMoveString[2]++;
7336                break;
7337              case WhiteHSideCastleFR:
7338              case BlackHSideCastleFR:
7339                toX--;
7340                currentMoveString[2]--;
7341                break;
7342              default: ; // nothing to do, but suppresses warning of pedantic compilers
7343            }
7344         }
7345         hintRequested = FALSE;
7346         lastHint[0] = NULLCHAR;
7347         bookRequested = FALSE;
7348         /* Program may be pondering now */
7349         cps->maybeThinking = TRUE;
7350         if (cps->sendTime == 2) cps->sendTime = 1;
7351         if (cps->offeredDraw) cps->offeredDraw--;
7352
7353         /* [AS] Save move info*/
7354         pvInfoList[ forwardMostMove ].score = programStats.score;
7355         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7356         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7357
7358         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7359
7360         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7361         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7362             int count = 0;
7363
7364             while( count < adjudicateLossPlies ) {
7365                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7366
7367                 if( count & 1 ) {
7368                     score = -score; /* Flip score for winning side */
7369                 }
7370
7371                 if( score > adjudicateLossThreshold ) {
7372                     break;
7373                 }
7374
7375                 count++;
7376             }
7377
7378             if( count >= adjudicateLossPlies ) {
7379                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7380
7381                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7382                     "Xboard adjudication",
7383                     GE_XBOARD );
7384
7385                 return;
7386             }
7387         }
7388
7389         if(Adjudicate(cps)) {
7390             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7391             return; // [HGM] adjudicate: for all automatic game ends
7392         }
7393
7394 #if ZIPPY
7395         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7396             first.initDone) {
7397           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7398                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7399                 SendToICS("draw ");
7400                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7401           }
7402           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7403           ics_user_moved = 1;
7404           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7405                 char buf[3*MSG_SIZ];
7406
7407                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7408                         programStats.score / 100.,
7409                         programStats.depth,
7410                         programStats.time / 100.,
7411                         (unsigned int)programStats.nodes,
7412                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7413                         programStats.movelist);
7414                 SendToICS(buf);
7415 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7416           }
7417         }
7418 #endif
7419
7420         /* [AS] Clear stats for next move */
7421         ClearProgramStats();
7422         thinkOutput[0] = NULLCHAR;
7423         hiddenThinkOutputState = 0;
7424
7425         bookHit = NULL;
7426         if (gameMode == TwoMachinesPlay) {
7427             /* [HGM] relaying draw offers moved to after reception of move */
7428             /* and interpreting offer as claim if it brings draw condition */
7429             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7430                 SendToProgram("draw\n", cps->other);
7431             }
7432             if (cps->other->sendTime) {
7433                 SendTimeRemaining(cps->other,
7434                                   cps->other->twoMachinesColor[0] == 'w');
7435             }
7436             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7437             if (firstMove && !bookHit) {
7438                 firstMove = FALSE;
7439                 if (cps->other->useColors) {
7440                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7441                 }
7442                 SendToProgram("go\n", cps->other);
7443             }
7444             cps->other->maybeThinking = TRUE;
7445         }
7446
7447         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7448
7449         if (!pausing && appData.ringBellAfterMoves) {
7450             RingBell();
7451         }
7452
7453         /*
7454          * Reenable menu items that were disabled while
7455          * machine was thinking
7456          */
7457         if (gameMode != TwoMachinesPlay)
7458             SetUserThinkingEnables();
7459
7460         // [HGM] book: after book hit opponent has received move and is now in force mode
7461         // force the book reply into it, and then fake that it outputted this move by jumping
7462         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7463         if(bookHit) {
7464                 static char bookMove[MSG_SIZ]; // a bit generous?
7465
7466                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7467                 strcat(bookMove, bookHit);
7468                 message = bookMove;
7469                 cps = cps->other;
7470                 programStats.nodes = programStats.depth = programStats.time =
7471                 programStats.score = programStats.got_only_move = 0;
7472                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7473
7474                 if(cps->lastPing != cps->lastPong) {
7475                     savedMessage = message; // args for deferred call
7476                     savedState = cps;
7477                     ScheduleDelayedEvent(DeferredBookMove, 10);
7478                     return;
7479                 }
7480                 goto FakeBookMove;
7481         }
7482
7483         return;
7484     }
7485
7486     /* Set special modes for chess engines.  Later something general
7487      *  could be added here; for now there is just one kludge feature,
7488      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7489      *  when "xboard" is given as an interactive command.
7490      */
7491     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7492         cps->useSigint = FALSE;
7493         cps->useSigterm = FALSE;
7494     }
7495     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7496       ParseFeatures(message+8, cps);
7497       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7498     }
7499
7500     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7501       int dummy, s=6; char buf[MSG_SIZ];
7502       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7503       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7504       ParseFEN(boards[0], &dummy, message+s);
7505       DrawPosition(TRUE, boards[0]);
7506       startedFromSetupPosition = TRUE;
7507       return;
7508     }
7509     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7510      * want this, I was asked to put it in, and obliged.
7511      */
7512     if (!strncmp(message, "setboard ", 9)) {
7513         Board initial_position;
7514
7515         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7516
7517         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7518             DisplayError(_("Bad FEN received from engine"), 0);
7519             return ;
7520         } else {
7521            Reset(TRUE, FALSE);
7522            CopyBoard(boards[0], initial_position);
7523            initialRulePlies = FENrulePlies;
7524            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7525            else gameMode = MachinePlaysBlack;
7526            DrawPosition(FALSE, boards[currentMove]);
7527         }
7528         return;
7529     }
7530
7531     /*
7532      * Look for communication commands
7533      */
7534     if (!strncmp(message, "telluser ", 9)) {
7535         if(message[9] == '\\' && message[10] == '\\')
7536             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7537         DisplayNote(message + 9);
7538         return;
7539     }
7540     if (!strncmp(message, "tellusererror ", 14)) {
7541         cps->userError = 1;
7542         if(message[14] == '\\' && message[15] == '\\')
7543             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7544         DisplayError(message + 14, 0);
7545         return;
7546     }
7547     if (!strncmp(message, "tellopponent ", 13)) {
7548       if (appData.icsActive) {
7549         if (loggedOn) {
7550           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7551           SendToICS(buf1);
7552         }
7553       } else {
7554         DisplayNote(message + 13);
7555       }
7556       return;
7557     }
7558     if (!strncmp(message, "tellothers ", 11)) {
7559       if (appData.icsActive) {
7560         if (loggedOn) {
7561           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7562           SendToICS(buf1);
7563         }
7564       }
7565       return;
7566     }
7567     if (!strncmp(message, "tellall ", 8)) {
7568       if (appData.icsActive) {
7569         if (loggedOn) {
7570           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7571           SendToICS(buf1);
7572         }
7573       } else {
7574         DisplayNote(message + 8);
7575       }
7576       return;
7577     }
7578     if (strncmp(message, "warning", 7) == 0) {
7579         /* Undocumented feature, use tellusererror in new code */
7580         DisplayError(message, 0);
7581         return;
7582     }
7583     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7584         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7585         strcat(realname, " query");
7586         AskQuestion(realname, buf2, buf1, cps->pr);
7587         return;
7588     }
7589     /* Commands from the engine directly to ICS.  We don't allow these to be
7590      *  sent until we are logged on. Crafty kibitzes have been known to
7591      *  interfere with the login process.
7592      */
7593     if (loggedOn) {
7594         if (!strncmp(message, "tellics ", 8)) {
7595             SendToICS(message + 8);
7596             SendToICS("\n");
7597             return;
7598         }
7599         if (!strncmp(message, "tellicsnoalias ", 15)) {
7600             SendToICS(ics_prefix);
7601             SendToICS(message + 15);
7602             SendToICS("\n");
7603             return;
7604         }
7605         /* The following are for backward compatibility only */
7606         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7607             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7608             SendToICS(ics_prefix);
7609             SendToICS(message);
7610             SendToICS("\n");
7611             return;
7612         }
7613     }
7614     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7615         return;
7616     }
7617     /*
7618      * If the move is illegal, cancel it and redraw the board.
7619      * Also deal with other error cases.  Matching is rather loose
7620      * here to accommodate engines written before the spec.
7621      */
7622     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7623         strncmp(message, "Error", 5) == 0) {
7624         if (StrStr(message, "name") ||
7625             StrStr(message, "rating") || StrStr(message, "?") ||
7626             StrStr(message, "result") || StrStr(message, "board") ||
7627             StrStr(message, "bk") || StrStr(message, "computer") ||
7628             StrStr(message, "variant") || StrStr(message, "hint") ||
7629             StrStr(message, "random") || StrStr(message, "depth") ||
7630             StrStr(message, "accepted")) {
7631             return;
7632         }
7633         if (StrStr(message, "protover")) {
7634           /* Program is responding to input, so it's apparently done
7635              initializing, and this error message indicates it is
7636              protocol version 1.  So we don't need to wait any longer
7637              for it to initialize and send feature commands. */
7638           FeatureDone(cps, 1);
7639           cps->protocolVersion = 1;
7640           return;
7641         }
7642         cps->maybeThinking = FALSE;
7643
7644         if (StrStr(message, "draw")) {
7645             /* Program doesn't have "draw" command */
7646             cps->sendDrawOffers = 0;
7647             return;
7648         }
7649         if (cps->sendTime != 1 &&
7650             (StrStr(message, "time") || StrStr(message, "otim"))) {
7651           /* Program apparently doesn't have "time" or "otim" command */
7652           cps->sendTime = 0;
7653           return;
7654         }
7655         if (StrStr(message, "analyze")) {
7656             cps->analysisSupport = FALSE;
7657             cps->analyzing = FALSE;
7658             Reset(FALSE, TRUE);
7659             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7660             DisplayError(buf2, 0);
7661             return;
7662         }
7663         if (StrStr(message, "(no matching move)st")) {
7664           /* Special kludge for GNU Chess 4 only */
7665           cps->stKludge = TRUE;
7666           SendTimeControl(cps, movesPerSession, timeControl,
7667                           timeIncrement, appData.searchDepth,
7668                           searchTime);
7669           return;
7670         }
7671         if (StrStr(message, "(no matching move)sd")) {
7672           /* Special kludge for GNU Chess 4 only */
7673           cps->sdKludge = TRUE;
7674           SendTimeControl(cps, movesPerSession, timeControl,
7675                           timeIncrement, appData.searchDepth,
7676                           searchTime);
7677           return;
7678         }
7679         if (!StrStr(message, "llegal")) {
7680             return;
7681         }
7682         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7683             gameMode == IcsIdle) return;
7684         if (forwardMostMove <= backwardMostMove) return;
7685         if (pausing) PauseEvent();
7686       if(appData.forceIllegal) {
7687             // [HGM] illegal: machine refused move; force position after move into it
7688           SendToProgram("force\n", cps);
7689           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7690                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7691                 // when black is to move, while there might be nothing on a2 or black
7692                 // might already have the move. So send the board as if white has the move.
7693                 // But first we must change the stm of the engine, as it refused the last move
7694                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7695                 if(WhiteOnMove(forwardMostMove)) {
7696                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7697                     SendBoard(cps, forwardMostMove); // kludgeless board
7698                 } else {
7699                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7700                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7701                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7702                 }
7703           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7704             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7705                  gameMode == TwoMachinesPlay)
7706               SendToProgram("go\n", cps);
7707             return;
7708       } else
7709         if (gameMode == PlayFromGameFile) {
7710             /* Stop reading this game file */
7711             gameMode = EditGame;
7712             ModeHighlight();
7713         }
7714         /* [HGM] illegal-move claim should forfeit game when Xboard */
7715         /* only passes fully legal moves                            */
7716         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7717             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7718                                 "False illegal-move claim", GE_XBOARD );
7719             return; // do not take back move we tested as valid
7720         }
7721         currentMove = forwardMostMove-1;
7722         DisplayMove(currentMove-1); /* before DisplayMoveError */
7723         SwitchClocks(forwardMostMove-1); // [HGM] race
7724         DisplayBothClocks();
7725         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7726                 parseList[currentMove], _(cps->which));
7727         DisplayMoveError(buf1);
7728         DrawPosition(FALSE, boards[currentMove]);
7729         return;
7730     }
7731     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7732         /* Program has a broken "time" command that
7733            outputs a string not ending in newline.
7734            Don't use it. */
7735         cps->sendTime = 0;
7736     }
7737
7738     /*
7739      * If chess program startup fails, exit with an error message.
7740      * Attempts to recover here are futile.
7741      */
7742     if ((StrStr(message, "unknown host") != NULL)
7743         || (StrStr(message, "No remote directory") != NULL)
7744         || (StrStr(message, "not found") != NULL)
7745         || (StrStr(message, "No such file") != NULL)
7746         || (StrStr(message, "can't alloc") != NULL)
7747         || (StrStr(message, "Permission denied") != NULL)) {
7748
7749         cps->maybeThinking = FALSE;
7750         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7751                 _(cps->which), cps->program, cps->host, message);
7752         RemoveInputSource(cps->isr);
7753         DisplayFatalError(buf1, 0, 1);
7754         return;
7755     }
7756
7757     /*
7758      * Look for hint output
7759      */
7760     if (sscanf(message, "Hint: %s", buf1) == 1) {
7761         if (cps == &first && hintRequested) {
7762             hintRequested = FALSE;
7763             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7764                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7765                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7766                                     PosFlags(forwardMostMove),
7767                                     fromY, fromX, toY, toX, promoChar, buf1);
7768                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7769                 DisplayInformation(buf2);
7770             } else {
7771                 /* Hint move could not be parsed!? */
7772               snprintf(buf2, sizeof(buf2),
7773                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7774                         buf1, _(cps->which));
7775                 DisplayError(buf2, 0);
7776             }
7777         } else {
7778           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7779         }
7780         return;
7781     }
7782
7783     /*
7784      * Ignore other messages if game is not in progress
7785      */
7786     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7787         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7788
7789     /*
7790      * look for win, lose, draw, or draw offer
7791      */
7792     if (strncmp(message, "1-0", 3) == 0) {
7793         char *p, *q, *r = "";
7794         p = strchr(message, '{');
7795         if (p) {
7796             q = strchr(p, '}');
7797             if (q) {
7798                 *q = NULLCHAR;
7799                 r = p + 1;
7800             }
7801         }
7802         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7803         return;
7804     } else if (strncmp(message, "0-1", 3) == 0) {
7805         char *p, *q, *r = "";
7806         p = strchr(message, '{');
7807         if (p) {
7808             q = strchr(p, '}');
7809             if (q) {
7810                 *q = NULLCHAR;
7811                 r = p + 1;
7812             }
7813         }
7814         /* Kludge for Arasan 4.1 bug */
7815         if (strcmp(r, "Black resigns") == 0) {
7816             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7817             return;
7818         }
7819         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7820         return;
7821     } else if (strncmp(message, "1/2", 3) == 0) {
7822         char *p, *q, *r = "";
7823         p = strchr(message, '{');
7824         if (p) {
7825             q = strchr(p, '}');
7826             if (q) {
7827                 *q = NULLCHAR;
7828                 r = p + 1;
7829             }
7830         }
7831
7832         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7833         return;
7834
7835     } else if (strncmp(message, "White resign", 12) == 0) {
7836         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7837         return;
7838     } else if (strncmp(message, "Black resign", 12) == 0) {
7839         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7840         return;
7841     } else if (strncmp(message, "White matches", 13) == 0 ||
7842                strncmp(message, "Black matches", 13) == 0   ) {
7843         /* [HGM] ignore GNUShogi noises */
7844         return;
7845     } else if (strncmp(message, "White", 5) == 0 &&
7846                message[5] != '(' &&
7847                StrStr(message, "Black") == NULL) {
7848         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7849         return;
7850     } else if (strncmp(message, "Black", 5) == 0 &&
7851                message[5] != '(') {
7852         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7853         return;
7854     } else if (strcmp(message, "resign") == 0 ||
7855                strcmp(message, "computer resigns") == 0) {
7856         switch (gameMode) {
7857           case MachinePlaysBlack:
7858           case IcsPlayingBlack:
7859             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7860             break;
7861           case MachinePlaysWhite:
7862           case IcsPlayingWhite:
7863             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7864             break;
7865           case TwoMachinesPlay:
7866             if (cps->twoMachinesColor[0] == 'w')
7867               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7868             else
7869               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7870             break;
7871           default:
7872             /* can't happen */
7873             break;
7874         }
7875         return;
7876     } else if (strncmp(message, "opponent mates", 14) == 0) {
7877         switch (gameMode) {
7878           case MachinePlaysBlack:
7879           case IcsPlayingBlack:
7880             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7881             break;
7882           case MachinePlaysWhite:
7883           case IcsPlayingWhite:
7884             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7885             break;
7886           case TwoMachinesPlay:
7887             if (cps->twoMachinesColor[0] == 'w')
7888               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7889             else
7890               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7891             break;
7892           default:
7893             /* can't happen */
7894             break;
7895         }
7896         return;
7897     } else if (strncmp(message, "computer mates", 14) == 0) {
7898         switch (gameMode) {
7899           case MachinePlaysBlack:
7900           case IcsPlayingBlack:
7901             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7902             break;
7903           case MachinePlaysWhite:
7904           case IcsPlayingWhite:
7905             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7906             break;
7907           case TwoMachinesPlay:
7908             if (cps->twoMachinesColor[0] == 'w')
7909               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7910             else
7911               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7912             break;
7913           default:
7914             /* can't happen */
7915             break;
7916         }
7917         return;
7918     } else if (strncmp(message, "checkmate", 9) == 0) {
7919         if (WhiteOnMove(forwardMostMove)) {
7920             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7921         } else {
7922             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7923         }
7924         return;
7925     } else if (strstr(message, "Draw") != NULL ||
7926                strstr(message, "game is a draw") != NULL) {
7927         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7928         return;
7929     } else if (strstr(message, "offer") != NULL &&
7930                strstr(message, "draw") != NULL) {
7931 #if ZIPPY
7932         if (appData.zippyPlay && first.initDone) {
7933             /* Relay offer to ICS */
7934             SendToICS(ics_prefix);
7935             SendToICS("draw\n");
7936         }
7937 #endif
7938         cps->offeredDraw = 2; /* valid until this engine moves twice */
7939         if (gameMode == TwoMachinesPlay) {
7940             if (cps->other->offeredDraw) {
7941                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7942             /* [HGM] in two-machine mode we delay relaying draw offer      */
7943             /* until after we also have move, to see if it is really claim */
7944             }
7945         } else if (gameMode == MachinePlaysWhite ||
7946                    gameMode == MachinePlaysBlack) {
7947           if (userOfferedDraw) {
7948             DisplayInformation(_("Machine accepts your draw offer"));
7949             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7950           } else {
7951             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7952           }
7953         }
7954     }
7955
7956
7957     /*
7958      * Look for thinking output
7959      */
7960     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7961           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7962                                 ) {
7963         int plylev, mvleft, mvtot, curscore, time;
7964         char mvname[MOVE_LEN];
7965         u64 nodes; // [DM]
7966         char plyext;
7967         int ignore = FALSE;
7968         int prefixHint = FALSE;
7969         mvname[0] = NULLCHAR;
7970
7971         switch (gameMode) {
7972           case MachinePlaysBlack:
7973           case IcsPlayingBlack:
7974             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7975             break;
7976           case MachinePlaysWhite:
7977           case IcsPlayingWhite:
7978             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7979             break;
7980           case AnalyzeMode:
7981           case AnalyzeFile:
7982             break;
7983           case IcsObserving: /* [DM] icsEngineAnalyze */
7984             if (!appData.icsEngineAnalyze) ignore = TRUE;
7985             break;
7986           case TwoMachinesPlay:
7987             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7988                 ignore = TRUE;
7989             }
7990             break;
7991           default:
7992             ignore = TRUE;
7993             break;
7994         }
7995
7996         if (!ignore) {
7997             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7998             buf1[0] = NULLCHAR;
7999             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8000                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8001
8002                 if (plyext != ' ' && plyext != '\t') {
8003                     time *= 100;
8004                 }
8005
8006                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8007                 if( cps->scoreIsAbsolute &&
8008                     ( gameMode == MachinePlaysBlack ||
8009                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8010                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8011                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8012                      !WhiteOnMove(currentMove)
8013                     ) )
8014                 {
8015                     curscore = -curscore;
8016                 }
8017
8018
8019                 tempStats.depth = plylev;
8020                 tempStats.nodes = nodes;
8021                 tempStats.time = time;
8022                 tempStats.score = curscore;
8023                 tempStats.got_only_move = 0;
8024
8025                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8026                         int ticklen;
8027
8028                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8029                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8030                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8031                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8032                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8033                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8034                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8035                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8036                 }
8037
8038                 /* Buffer overflow protection */
8039                 if (buf1[0] != NULLCHAR) {
8040                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8041                         && appData.debugMode) {
8042                         fprintf(debugFP,
8043                                 "PV is too long; using the first %u bytes.\n",
8044                                 (unsigned) sizeof(tempStats.movelist) - 1);
8045                     }
8046
8047                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8048                 } else {
8049                     sprintf(tempStats.movelist, " no PV\n");
8050                 }
8051
8052                 if (tempStats.seen_stat) {
8053                     tempStats.ok_to_send = 1;
8054                 }
8055
8056                 if (strchr(tempStats.movelist, '(') != NULL) {
8057                     tempStats.line_is_book = 1;
8058                     tempStats.nr_moves = 0;
8059                     tempStats.moves_left = 0;
8060                 } else {
8061                     tempStats.line_is_book = 0;
8062                 }
8063
8064                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8065                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8066
8067                 SendProgramStatsToFrontend( cps, &tempStats );
8068
8069                 /*
8070                     [AS] Protect the thinkOutput buffer from overflow... this
8071                     is only useful if buf1 hasn't overflowed first!
8072                 */
8073                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8074                          plylev,
8075                          (gameMode == TwoMachinesPlay ?
8076                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8077                          ((double) curscore) / 100.0,
8078                          prefixHint ? lastHint : "",
8079                          prefixHint ? " " : "" );
8080
8081                 if( buf1[0] != NULLCHAR ) {
8082                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8083
8084                     if( strlen(buf1) > max_len ) {
8085                         if( appData.debugMode) {
8086                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8087                         }
8088                         buf1[max_len+1] = '\0';
8089                     }
8090
8091                     strcat( thinkOutput, buf1 );
8092                 }
8093
8094                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8095                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8096                     DisplayMove(currentMove - 1);
8097                 }
8098                 return;
8099
8100             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8101                 /* crafty (9.25+) says "(only move) <move>"
8102                  * if there is only 1 legal move
8103                  */
8104                 sscanf(p, "(only move) %s", buf1);
8105                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8106                 sprintf(programStats.movelist, "%s (only move)", buf1);
8107                 programStats.depth = 1;
8108                 programStats.nr_moves = 1;
8109                 programStats.moves_left = 1;
8110                 programStats.nodes = 1;
8111                 programStats.time = 1;
8112                 programStats.got_only_move = 1;
8113
8114                 /* Not really, but we also use this member to
8115                    mean "line isn't going to change" (Crafty
8116                    isn't searching, so stats won't change) */
8117                 programStats.line_is_book = 1;
8118
8119                 SendProgramStatsToFrontend( cps, &programStats );
8120
8121                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8122                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8123                     DisplayMove(currentMove - 1);
8124                 }
8125                 return;
8126             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8127                               &time, &nodes, &plylev, &mvleft,
8128                               &mvtot, mvname) >= 5) {
8129                 /* The stat01: line is from Crafty (9.29+) in response
8130                    to the "." command */
8131                 programStats.seen_stat = 1;
8132                 cps->maybeThinking = TRUE;
8133
8134                 if (programStats.got_only_move || !appData.periodicUpdates)
8135                   return;
8136
8137                 programStats.depth = plylev;
8138                 programStats.time = time;
8139                 programStats.nodes = nodes;
8140                 programStats.moves_left = mvleft;
8141                 programStats.nr_moves = mvtot;
8142                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8143                 programStats.ok_to_send = 1;
8144                 programStats.movelist[0] = '\0';
8145
8146                 SendProgramStatsToFrontend( cps, &programStats );
8147
8148                 return;
8149
8150             } else if (strncmp(message,"++",2) == 0) {
8151                 /* Crafty 9.29+ outputs this */
8152                 programStats.got_fail = 2;
8153                 return;
8154
8155             } else if (strncmp(message,"--",2) == 0) {
8156                 /* Crafty 9.29+ outputs this */
8157                 programStats.got_fail = 1;
8158                 return;
8159
8160             } else if (thinkOutput[0] != NULLCHAR &&
8161                        strncmp(message, "    ", 4) == 0) {
8162                 unsigned message_len;
8163
8164                 p = message;
8165                 while (*p && *p == ' ') p++;
8166
8167                 message_len = strlen( p );
8168
8169                 /* [AS] Avoid buffer overflow */
8170                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8171                     strcat(thinkOutput, " ");
8172                     strcat(thinkOutput, p);
8173                 }
8174
8175                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8176                     strcat(programStats.movelist, " ");
8177                     strcat(programStats.movelist, p);
8178                 }
8179
8180                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8181                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8182                     DisplayMove(currentMove - 1);
8183                 }
8184                 return;
8185             }
8186         }
8187         else {
8188             buf1[0] = NULLCHAR;
8189
8190             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8191                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8192             {
8193                 ChessProgramStats cpstats;
8194
8195                 if (plyext != ' ' && plyext != '\t') {
8196                     time *= 100;
8197                 }
8198
8199                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8200                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8201                     curscore = -curscore;
8202                 }
8203
8204                 cpstats.depth = plylev;
8205                 cpstats.nodes = nodes;
8206                 cpstats.time = time;
8207                 cpstats.score = curscore;
8208                 cpstats.got_only_move = 0;
8209                 cpstats.movelist[0] = '\0';
8210
8211                 if (buf1[0] != NULLCHAR) {
8212                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8213                 }
8214
8215                 cpstats.ok_to_send = 0;
8216                 cpstats.line_is_book = 0;
8217                 cpstats.nr_moves = 0;
8218                 cpstats.moves_left = 0;
8219
8220                 SendProgramStatsToFrontend( cps, &cpstats );
8221             }
8222         }
8223     }
8224 }
8225
8226
8227 /* Parse a game score from the character string "game", and
8228    record it as the history of the current game.  The game
8229    score is NOT assumed to start from the standard position.
8230    The display is not updated in any way.
8231    */
8232 void
8233 ParseGameHistory(game)
8234      char *game;
8235 {
8236     ChessMove moveType;
8237     int fromX, fromY, toX, toY, boardIndex;
8238     char promoChar;
8239     char *p, *q;
8240     char buf[MSG_SIZ];
8241
8242     if (appData.debugMode)
8243       fprintf(debugFP, "Parsing game history: %s\n", game);
8244
8245     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8246     gameInfo.site = StrSave(appData.icsHost);
8247     gameInfo.date = PGNDate();
8248     gameInfo.round = StrSave("-");
8249
8250     /* Parse out names of players */
8251     while (*game == ' ') game++;
8252     p = buf;
8253     while (*game != ' ') *p++ = *game++;
8254     *p = NULLCHAR;
8255     gameInfo.white = StrSave(buf);
8256     while (*game == ' ') game++;
8257     p = buf;
8258     while (*game != ' ' && *game != '\n') *p++ = *game++;
8259     *p = NULLCHAR;
8260     gameInfo.black = StrSave(buf);
8261
8262     /* Parse moves */
8263     boardIndex = blackPlaysFirst ? 1 : 0;
8264     yynewstr(game);
8265     for (;;) {
8266         yyboardindex = boardIndex;
8267         moveType = (ChessMove) Myylex();
8268         switch (moveType) {
8269           case IllegalMove:             /* maybe suicide chess, etc. */
8270   if (appData.debugMode) {
8271     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8272     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8273     setbuf(debugFP, NULL);
8274   }
8275           case WhitePromotion:
8276           case BlackPromotion:
8277           case WhiteNonPromotion:
8278           case BlackNonPromotion:
8279           case NormalMove:
8280           case WhiteCapturesEnPassant:
8281           case BlackCapturesEnPassant:
8282           case WhiteKingSideCastle:
8283           case WhiteQueenSideCastle:
8284           case BlackKingSideCastle:
8285           case BlackQueenSideCastle:
8286           case WhiteKingSideCastleWild:
8287           case WhiteQueenSideCastleWild:
8288           case BlackKingSideCastleWild:
8289           case BlackQueenSideCastleWild:
8290           /* PUSH Fabien */
8291           case WhiteHSideCastleFR:
8292           case WhiteASideCastleFR:
8293           case BlackHSideCastleFR:
8294           case BlackASideCastleFR:
8295           /* POP Fabien */
8296             fromX = currentMoveString[0] - AAA;
8297             fromY = currentMoveString[1] - ONE;
8298             toX = currentMoveString[2] - AAA;
8299             toY = currentMoveString[3] - ONE;
8300             promoChar = currentMoveString[4];
8301             break;
8302           case WhiteDrop:
8303           case BlackDrop:
8304             fromX = moveType == WhiteDrop ?
8305               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8306             (int) CharToPiece(ToLower(currentMoveString[0]));
8307             fromY = DROP_RANK;
8308             toX = currentMoveString[2] - AAA;
8309             toY = currentMoveString[3] - ONE;
8310             promoChar = NULLCHAR;
8311             break;
8312           case AmbiguousMove:
8313             /* bug? */
8314             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8315   if (appData.debugMode) {
8316     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8317     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8318     setbuf(debugFP, NULL);
8319   }
8320             DisplayError(buf, 0);
8321             return;
8322           case ImpossibleMove:
8323             /* bug? */
8324             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8325   if (appData.debugMode) {
8326     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8327     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8328     setbuf(debugFP, NULL);
8329   }
8330             DisplayError(buf, 0);
8331             return;
8332           case EndOfFile:
8333             if (boardIndex < backwardMostMove) {
8334                 /* Oops, gap.  How did that happen? */
8335                 DisplayError(_("Gap in move list"), 0);
8336                 return;
8337             }
8338             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8339             if (boardIndex > forwardMostMove) {
8340                 forwardMostMove = boardIndex;
8341             }
8342             return;
8343           case ElapsedTime:
8344             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8345                 strcat(parseList[boardIndex-1], " ");
8346                 strcat(parseList[boardIndex-1], yy_text);
8347             }
8348             continue;
8349           case Comment:
8350           case PGNTag:
8351           case NAG:
8352           default:
8353             /* ignore */
8354             continue;
8355           case WhiteWins:
8356           case BlackWins:
8357           case GameIsDrawn:
8358           case GameUnfinished:
8359             if (gameMode == IcsExamining) {
8360                 if (boardIndex < backwardMostMove) {
8361                     /* Oops, gap.  How did that happen? */
8362                     return;
8363                 }
8364                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8365                 return;
8366             }
8367             gameInfo.result = moveType;
8368             p = strchr(yy_text, '{');
8369             if (p == NULL) p = strchr(yy_text, '(');
8370             if (p == NULL) {
8371                 p = yy_text;
8372                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8373             } else {
8374                 q = strchr(p, *p == '{' ? '}' : ')');
8375                 if (q != NULL) *q = NULLCHAR;
8376                 p++;
8377             }
8378             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8379             gameInfo.resultDetails = StrSave(p);
8380             continue;
8381         }
8382         if (boardIndex >= forwardMostMove &&
8383             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8384             backwardMostMove = blackPlaysFirst ? 1 : 0;
8385             return;
8386         }
8387         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8388                                  fromY, fromX, toY, toX, promoChar,
8389                                  parseList[boardIndex]);
8390         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8391         /* currentMoveString is set as a side-effect of yylex */
8392         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8393         strcat(moveList[boardIndex], "\n");
8394         boardIndex++;
8395         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8396         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8397           case MT_NONE:
8398           case MT_STALEMATE:
8399           default:
8400             break;
8401           case MT_CHECK:
8402             if(gameInfo.variant != VariantShogi)
8403                 strcat(parseList[boardIndex - 1], "+");
8404             break;
8405           case MT_CHECKMATE:
8406           case MT_STAINMATE:
8407             strcat(parseList[boardIndex - 1], "#");
8408             break;
8409         }
8410     }
8411 }
8412
8413
8414 /* Apply a move to the given board  */
8415 void
8416 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8417      int fromX, fromY, toX, toY;
8418      int promoChar;
8419      Board board;
8420 {
8421   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8422   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8423
8424     /* [HGM] compute & store e.p. status and castling rights for new position */
8425     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8426
8427       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8428       oldEP = (signed char)board[EP_STATUS];
8429       board[EP_STATUS] = EP_NONE;
8430
8431       if( board[toY][toX] != EmptySquare )
8432            board[EP_STATUS] = EP_CAPTURE;
8433
8434   if (fromY == DROP_RANK) {
8435         /* must be first */
8436         piece = board[toY][toX] = (ChessSquare) fromX;
8437   } else {
8438       int i;
8439
8440       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8441            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8442                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8443       } else
8444       if( board[fromY][fromX] == WhitePawn ) {
8445            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8446                board[EP_STATUS] = EP_PAWN_MOVE;
8447            if( toY-fromY==2) {
8448                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8449                         gameInfo.variant != VariantBerolina || toX < fromX)
8450                       board[EP_STATUS] = toX | berolina;
8451                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8452                         gameInfo.variant != VariantBerolina || toX > fromX)
8453                       board[EP_STATUS] = toX;
8454            }
8455       } else
8456       if( board[fromY][fromX] == BlackPawn ) {
8457            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8458                board[EP_STATUS] = EP_PAWN_MOVE;
8459            if( toY-fromY== -2) {
8460                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8461                         gameInfo.variant != VariantBerolina || toX < fromX)
8462                       board[EP_STATUS] = toX | berolina;
8463                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8464                         gameInfo.variant != VariantBerolina || toX > fromX)
8465                       board[EP_STATUS] = toX;
8466            }
8467        }
8468
8469        for(i=0; i<nrCastlingRights; i++) {
8470            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8471               board[CASTLING][i] == toX   && castlingRank[i] == toY
8472              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8473        }
8474
8475      if (fromX == toX && fromY == toY) return;
8476
8477      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8478      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8479      if(gameInfo.variant == VariantKnightmate)
8480          king += (int) WhiteUnicorn - (int) WhiteKing;
8481
8482     /* Code added by Tord: */
8483     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8484     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8485         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8486       board[fromY][fromX] = EmptySquare;
8487       board[toY][toX] = EmptySquare;
8488       if((toX > fromX) != (piece == WhiteRook)) {
8489         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8490       } else {
8491         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8492       }
8493     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8494                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8495       board[fromY][fromX] = EmptySquare;
8496       board[toY][toX] = EmptySquare;
8497       if((toX > fromX) != (piece == BlackRook)) {
8498         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8499       } else {
8500         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8501       }
8502     /* End of code added by Tord */
8503
8504     } else if (board[fromY][fromX] == king
8505         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8506         && toY == fromY && toX > fromX+1) {
8507         board[fromY][fromX] = EmptySquare;
8508         board[toY][toX] = king;
8509         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8510         board[fromY][BOARD_RGHT-1] = EmptySquare;
8511     } else if (board[fromY][fromX] == king
8512         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8513                && toY == fromY && toX < fromX-1) {
8514         board[fromY][fromX] = EmptySquare;
8515         board[toY][toX] = king;
8516         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8517         board[fromY][BOARD_LEFT] = EmptySquare;
8518     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8519                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8520                && toY >= BOARD_HEIGHT-promoRank
8521                ) {
8522         /* white pawn promotion */
8523         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8524         if (board[toY][toX] == EmptySquare) {
8525             board[toY][toX] = WhiteQueen;
8526         }
8527         if(gameInfo.variant==VariantBughouse ||
8528            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8529             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8530         board[fromY][fromX] = EmptySquare;
8531     } else if ((fromY == BOARD_HEIGHT-4)
8532                && (toX != fromX)
8533                && gameInfo.variant != VariantXiangqi
8534                && gameInfo.variant != VariantBerolina
8535                && (board[fromY][fromX] == WhitePawn)
8536                && (board[toY][toX] == EmptySquare)) {
8537         board[fromY][fromX] = EmptySquare;
8538         board[toY][toX] = WhitePawn;
8539         captured = board[toY - 1][toX];
8540         board[toY - 1][toX] = EmptySquare;
8541     } else if ((fromY == BOARD_HEIGHT-4)
8542                && (toX == fromX)
8543                && gameInfo.variant == VariantBerolina
8544                && (board[fromY][fromX] == WhitePawn)
8545                && (board[toY][toX] == EmptySquare)) {
8546         board[fromY][fromX] = EmptySquare;
8547         board[toY][toX] = WhitePawn;
8548         if(oldEP & EP_BEROLIN_A) {
8549                 captured = board[fromY][fromX-1];
8550                 board[fromY][fromX-1] = EmptySquare;
8551         }else{  captured = board[fromY][fromX+1];
8552                 board[fromY][fromX+1] = EmptySquare;
8553         }
8554     } else if (board[fromY][fromX] == king
8555         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8556                && toY == fromY && toX > fromX+1) {
8557         board[fromY][fromX] = EmptySquare;
8558         board[toY][toX] = king;
8559         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8560         board[fromY][BOARD_RGHT-1] = EmptySquare;
8561     } else if (board[fromY][fromX] == king
8562         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8563                && toY == fromY && toX < fromX-1) {
8564         board[fromY][fromX] = EmptySquare;
8565         board[toY][toX] = king;
8566         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8567         board[fromY][BOARD_LEFT] = EmptySquare;
8568     } else if (fromY == 7 && fromX == 3
8569                && board[fromY][fromX] == BlackKing
8570                && toY == 7 && toX == 5) {
8571         board[fromY][fromX] = EmptySquare;
8572         board[toY][toX] = BlackKing;
8573         board[fromY][7] = EmptySquare;
8574         board[toY][4] = BlackRook;
8575     } else if (fromY == 7 && fromX == 3
8576                && board[fromY][fromX] == BlackKing
8577                && toY == 7 && toX == 1) {
8578         board[fromY][fromX] = EmptySquare;
8579         board[toY][toX] = BlackKing;
8580         board[fromY][0] = EmptySquare;
8581         board[toY][2] = BlackRook;
8582     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8583                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8584                && toY < promoRank
8585                ) {
8586         /* black pawn promotion */
8587         board[toY][toX] = CharToPiece(ToLower(promoChar));
8588         if (board[toY][toX] == EmptySquare) {
8589             board[toY][toX] = BlackQueen;
8590         }
8591         if(gameInfo.variant==VariantBughouse ||
8592            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8593             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8594         board[fromY][fromX] = EmptySquare;
8595     } else if ((fromY == 3)
8596                && (toX != fromX)
8597                && gameInfo.variant != VariantXiangqi
8598                && gameInfo.variant != VariantBerolina
8599                && (board[fromY][fromX] == BlackPawn)
8600                && (board[toY][toX] == EmptySquare)) {
8601         board[fromY][fromX] = EmptySquare;
8602         board[toY][toX] = BlackPawn;
8603         captured = board[toY + 1][toX];
8604         board[toY + 1][toX] = EmptySquare;
8605     } else if ((fromY == 3)
8606                && (toX == fromX)
8607                && gameInfo.variant == VariantBerolina
8608                && (board[fromY][fromX] == BlackPawn)
8609                && (board[toY][toX] == EmptySquare)) {
8610         board[fromY][fromX] = EmptySquare;
8611         board[toY][toX] = BlackPawn;
8612         if(oldEP & EP_BEROLIN_A) {
8613                 captured = board[fromY][fromX-1];
8614                 board[fromY][fromX-1] = EmptySquare;
8615         }else{  captured = board[fromY][fromX+1];
8616                 board[fromY][fromX+1] = EmptySquare;
8617         }
8618     } else {
8619         board[toY][toX] = board[fromY][fromX];
8620         board[fromY][fromX] = EmptySquare;
8621     }
8622   }
8623
8624     if (gameInfo.holdingsWidth != 0) {
8625
8626       /* !!A lot more code needs to be written to support holdings  */
8627       /* [HGM] OK, so I have written it. Holdings are stored in the */
8628       /* penultimate board files, so they are automaticlly stored   */
8629       /* in the game history.                                       */
8630       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8631                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8632         /* Delete from holdings, by decreasing count */
8633         /* and erasing image if necessary            */
8634         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8635         if(p < (int) BlackPawn) { /* white drop */
8636              p -= (int)WhitePawn;
8637                  p = PieceToNumber((ChessSquare)p);
8638              if(p >= gameInfo.holdingsSize) p = 0;
8639              if(--board[p][BOARD_WIDTH-2] <= 0)
8640                   board[p][BOARD_WIDTH-1] = EmptySquare;
8641              if((int)board[p][BOARD_WIDTH-2] < 0)
8642                         board[p][BOARD_WIDTH-2] = 0;
8643         } else {                  /* black drop */
8644              p -= (int)BlackPawn;
8645                  p = PieceToNumber((ChessSquare)p);
8646              if(p >= gameInfo.holdingsSize) p = 0;
8647              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8648                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8649              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8650                         board[BOARD_HEIGHT-1-p][1] = 0;
8651         }
8652       }
8653       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8654           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8655         /* [HGM] holdings: Add to holdings, if holdings exist */
8656         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8657                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8658                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8659         }
8660         p = (int) captured;
8661         if (p >= (int) BlackPawn) {
8662           p -= (int)BlackPawn;
8663           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8664                   /* in Shogi restore piece to its original  first */
8665                   captured = (ChessSquare) (DEMOTED captured);
8666                   p = DEMOTED p;
8667           }
8668           p = PieceToNumber((ChessSquare)p);
8669           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8670           board[p][BOARD_WIDTH-2]++;
8671           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8672         } else {
8673           p -= (int)WhitePawn;
8674           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8675                   captured = (ChessSquare) (DEMOTED captured);
8676                   p = DEMOTED p;
8677           }
8678           p = PieceToNumber((ChessSquare)p);
8679           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8680           board[BOARD_HEIGHT-1-p][1]++;
8681           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8682         }
8683       }
8684     } else if (gameInfo.variant == VariantAtomic) {
8685       if (captured != EmptySquare) {
8686         int y, x;
8687         for (y = toY-1; y <= toY+1; y++) {
8688           for (x = toX-1; x <= toX+1; x++) {
8689             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8690                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8691               board[y][x] = EmptySquare;
8692             }
8693           }
8694         }
8695         board[toY][toX] = EmptySquare;
8696       }
8697     }
8698     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8699         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8700     } else
8701     if(promoChar == '+') {
8702         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8703         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8704     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8705         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8706     }
8707     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8708                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8709         // [HGM] superchess: take promotion piece out of holdings
8710         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8711         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8712             if(!--board[k][BOARD_WIDTH-2])
8713                 board[k][BOARD_WIDTH-1] = EmptySquare;
8714         } else {
8715             if(!--board[BOARD_HEIGHT-1-k][1])
8716                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8717         }
8718     }
8719
8720 }
8721
8722 /* Updates forwardMostMove */
8723 void
8724 MakeMove(fromX, fromY, toX, toY, promoChar)
8725      int fromX, fromY, toX, toY;
8726      int promoChar;
8727 {
8728 //    forwardMostMove++; // [HGM] bare: moved downstream
8729
8730     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8731         int timeLeft; static int lastLoadFlag=0; int king, piece;
8732         piece = boards[forwardMostMove][fromY][fromX];
8733         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8734         if(gameInfo.variant == VariantKnightmate)
8735             king += (int) WhiteUnicorn - (int) WhiteKing;
8736         if(forwardMostMove == 0) {
8737             if(blackPlaysFirst)
8738                 fprintf(serverMoves, "%s;", second.tidy);
8739             fprintf(serverMoves, "%s;", first.tidy);
8740             if(!blackPlaysFirst)
8741                 fprintf(serverMoves, "%s;", second.tidy);
8742         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8743         lastLoadFlag = loadFlag;
8744         // print base move
8745         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8746         // print castling suffix
8747         if( toY == fromY && piece == king ) {
8748             if(toX-fromX > 1)
8749                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8750             if(fromX-toX >1)
8751                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8752         }
8753         // e.p. suffix
8754         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8755              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8756              boards[forwardMostMove][toY][toX] == EmptySquare
8757              && fromX != toX && fromY != toY)
8758                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8759         // promotion suffix
8760         if(promoChar != NULLCHAR)
8761                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8762         if(!loadFlag) {
8763             fprintf(serverMoves, "/%d/%d",
8764                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8765             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8766             else                      timeLeft = blackTimeRemaining/1000;
8767             fprintf(serverMoves, "/%d", timeLeft);
8768         }
8769         fflush(serverMoves);
8770     }
8771
8772     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8773       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8774                         0, 1);
8775       return;
8776     }
8777     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8778     if (commentList[forwardMostMove+1] != NULL) {
8779         free(commentList[forwardMostMove+1]);
8780         commentList[forwardMostMove+1] = NULL;
8781     }
8782     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8783     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8784     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8785     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8786     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8787     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8788     gameInfo.result = GameUnfinished;
8789     if (gameInfo.resultDetails != NULL) {
8790         free(gameInfo.resultDetails);
8791         gameInfo.resultDetails = NULL;
8792     }
8793     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8794                               moveList[forwardMostMove - 1]);
8795     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8796                              PosFlags(forwardMostMove - 1),
8797                              fromY, fromX, toY, toX, promoChar,
8798                              parseList[forwardMostMove - 1]);
8799     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8800       case MT_NONE:
8801       case MT_STALEMATE:
8802       default:
8803         break;
8804       case MT_CHECK:
8805         if(gameInfo.variant != VariantShogi)
8806             strcat(parseList[forwardMostMove - 1], "+");
8807         break;
8808       case MT_CHECKMATE:
8809       case MT_STAINMATE:
8810         strcat(parseList[forwardMostMove - 1], "#");
8811         break;
8812     }
8813     if (appData.debugMode) {
8814         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8815     }
8816
8817 }
8818
8819 /* Updates currentMove if not pausing */
8820 void
8821 ShowMove(fromX, fromY, toX, toY)
8822 {
8823     int instant = (gameMode == PlayFromGameFile) ?
8824         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8825     if(appData.noGUI) return;
8826     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8827         if (!instant) {
8828             if (forwardMostMove == currentMove + 1) {
8829                 AnimateMove(boards[forwardMostMove - 1],
8830                             fromX, fromY, toX, toY);
8831             }
8832             if (appData.highlightLastMove) {
8833                 SetHighlights(fromX, fromY, toX, toY);
8834             }
8835         }
8836         currentMove = forwardMostMove;
8837     }
8838
8839     if (instant) return;
8840
8841     DisplayMove(currentMove - 1);
8842     DrawPosition(FALSE, boards[currentMove]);
8843     DisplayBothClocks();
8844     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8845 }
8846
8847 void SendEgtPath(ChessProgramState *cps)
8848 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8849         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8850
8851         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8852
8853         while(*p) {
8854             char c, *q = name+1, *r, *s;
8855
8856             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8857             while(*p && *p != ',') *q++ = *p++;
8858             *q++ = ':'; *q = 0;
8859             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8860                 strcmp(name, ",nalimov:") == 0 ) {
8861                 // take nalimov path from the menu-changeable option first, if it is defined
8862               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8863                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8864             } else
8865             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8866                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8867                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8868                 s = r = StrStr(s, ":") + 1; // beginning of path info
8869                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8870                 c = *r; *r = 0;             // temporarily null-terminate path info
8871                     *--q = 0;               // strip of trailig ':' from name
8872                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8873                 *r = c;
8874                 SendToProgram(buf,cps);     // send egtbpath command for this format
8875             }
8876             if(*p == ',') p++; // read away comma to position for next format name
8877         }
8878 }
8879
8880 void
8881 InitChessProgram(cps, setup)
8882      ChessProgramState *cps;
8883      int setup; /* [HGM] needed to setup FRC opening position */
8884 {
8885     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8886     if (appData.noChessProgram) return;
8887     hintRequested = FALSE;
8888     bookRequested = FALSE;
8889
8890     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8891     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8892     if(cps->memSize) { /* [HGM] memory */
8893       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8894         SendToProgram(buf, cps);
8895     }
8896     SendEgtPath(cps); /* [HGM] EGT */
8897     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8898       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8899         SendToProgram(buf, cps);
8900     }
8901
8902     SendToProgram(cps->initString, cps);
8903     if (gameInfo.variant != VariantNormal &&
8904         gameInfo.variant != VariantLoadable
8905         /* [HGM] also send variant if board size non-standard */
8906         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8907                                             ) {
8908       char *v = VariantName(gameInfo.variant);
8909       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8910         /* [HGM] in protocol 1 we have to assume all variants valid */
8911         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8912         DisplayFatalError(buf, 0, 1);
8913         return;
8914       }
8915
8916       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8917       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8918       if( gameInfo.variant == VariantXiangqi )
8919            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8920       if( gameInfo.variant == VariantShogi )
8921            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8922       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8923            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8924       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8925           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
8926            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8927       if( gameInfo.variant == VariantCourier )
8928            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8929       if( gameInfo.variant == VariantSuper )
8930            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8931       if( gameInfo.variant == VariantGreat )
8932            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8933       if( gameInfo.variant == VariantSChess )
8934            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8935
8936       if(overruled) {
8937         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8938                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8939            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8940            if(StrStr(cps->variants, b) == NULL) {
8941                // specific sized variant not known, check if general sizing allowed
8942                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8943                    if(StrStr(cps->variants, "boardsize") == NULL) {
8944                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8945                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8946                        DisplayFatalError(buf, 0, 1);
8947                        return;
8948                    }
8949                    /* [HGM] here we really should compare with the maximum supported board size */
8950                }
8951            }
8952       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8953       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8954       SendToProgram(buf, cps);
8955     }
8956     currentlyInitializedVariant = gameInfo.variant;
8957
8958     /* [HGM] send opening position in FRC to first engine */
8959     if(setup) {
8960           SendToProgram("force\n", cps);
8961           SendBoard(cps, 0);
8962           /* engine is now in force mode! Set flag to wake it up after first move. */
8963           setboardSpoiledMachineBlack = 1;
8964     }
8965
8966     if (cps->sendICS) {
8967       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8968       SendToProgram(buf, cps);
8969     }
8970     cps->maybeThinking = FALSE;
8971     cps->offeredDraw = 0;
8972     if (!appData.icsActive) {
8973         SendTimeControl(cps, movesPerSession, timeControl,
8974                         timeIncrement, appData.searchDepth,
8975                         searchTime);
8976     }
8977     if (appData.showThinking
8978         // [HGM] thinking: four options require thinking output to be sent
8979         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8980                                 ) {
8981         SendToProgram("post\n", cps);
8982     }
8983     SendToProgram("hard\n", cps);
8984     if (!appData.ponderNextMove) {
8985         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8986            it without being sure what state we are in first.  "hard"
8987            is not a toggle, so that one is OK.
8988          */
8989         SendToProgram("easy\n", cps);
8990     }
8991     if (cps->usePing) {
8992       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8993       SendToProgram(buf, cps);
8994     }
8995     cps->initDone = TRUE;
8996 }
8997
8998
8999 void
9000 StartChessProgram(cps)
9001      ChessProgramState *cps;
9002 {
9003     char buf[MSG_SIZ];
9004     int err;
9005
9006     if (appData.noChessProgram) return;
9007     cps->initDone = FALSE;
9008
9009     if (strcmp(cps->host, "localhost") == 0) {
9010         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9011     } else if (*appData.remoteShell == NULLCHAR) {
9012         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9013     } else {
9014         if (*appData.remoteUser == NULLCHAR) {
9015           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9016                     cps->program);
9017         } else {
9018           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9019                     cps->host, appData.remoteUser, cps->program);
9020         }
9021         err = StartChildProcess(buf, "", &cps->pr);
9022     }
9023
9024     if (err != 0) {
9025       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9026         DisplayFatalError(buf, err, 1);
9027         cps->pr = NoProc;
9028         cps->isr = NULL;
9029         return;
9030     }
9031
9032     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9033     if (cps->protocolVersion > 1) {
9034       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9035       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9036       cps->comboCnt = 0;  //                and values of combo boxes
9037       SendToProgram(buf, cps);
9038     } else {
9039       SendToProgram("xboard\n", cps);
9040     }
9041 }
9042
9043
9044 void
9045 TwoMachinesEventIfReady P((void))
9046 {
9047   if (first.lastPing != first.lastPong) {
9048     DisplayMessage("", _("Waiting for first chess program"));
9049     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9050     return;
9051   }
9052   if (second.lastPing != second.lastPong) {
9053     DisplayMessage("", _("Waiting for second chess program"));
9054     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9055     return;
9056   }
9057   ThawUI();
9058   TwoMachinesEvent();
9059 }
9060
9061 void
9062 NextMatchGame P((void))
9063 {
9064     int index; /* [HGM] autoinc: step load index during match */
9065     Reset(FALSE, TRUE);
9066     if (*appData.loadGameFile != NULLCHAR) {
9067         index = appData.loadGameIndex;
9068         if(index < 0) { // [HGM] autoinc
9069             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9070             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9071         }
9072         LoadGameFromFile(appData.loadGameFile,
9073                          index,
9074                          appData.loadGameFile, FALSE);
9075     } else if (*appData.loadPositionFile != NULLCHAR) {
9076         index = appData.loadPositionIndex;
9077         if(index < 0) { // [HGM] autoinc
9078             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9079             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9080         }
9081         LoadPositionFromFile(appData.loadPositionFile,
9082                              index,
9083                              appData.loadPositionFile);
9084     }
9085     TwoMachinesEventIfReady();
9086 }
9087
9088 void UserAdjudicationEvent( int result )
9089 {
9090     ChessMove gameResult = GameIsDrawn;
9091
9092     if( result > 0 ) {
9093         gameResult = WhiteWins;
9094     }
9095     else if( result < 0 ) {
9096         gameResult = BlackWins;
9097     }
9098
9099     if( gameMode == TwoMachinesPlay ) {
9100         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9101     }
9102 }
9103
9104
9105 // [HGM] save: calculate checksum of game to make games easily identifiable
9106 int StringCheckSum(char *s)
9107 {
9108         int i = 0;
9109         if(s==NULL) return 0;
9110         while(*s) i = i*259 + *s++;
9111         return i;
9112 }
9113
9114 int GameCheckSum()
9115 {
9116         int i, sum=0;
9117         for(i=backwardMostMove; i<forwardMostMove; i++) {
9118                 sum += pvInfoList[i].depth;
9119                 sum += StringCheckSum(parseList[i]);
9120                 sum += StringCheckSum(commentList[i]);
9121                 sum *= 261;
9122         }
9123         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9124         return sum + StringCheckSum(commentList[i]);
9125 } // end of save patch
9126
9127 void
9128 GameEnds(result, resultDetails, whosays)
9129      ChessMove result;
9130      char *resultDetails;
9131      int whosays;
9132 {
9133     GameMode nextGameMode;
9134     int isIcsGame;
9135     char buf[MSG_SIZ], popupRequested = 0;
9136
9137     if(endingGame) return; /* [HGM] crash: forbid recursion */
9138     endingGame = 1;
9139     if(twoBoards) { // [HGM] dual: switch back to one board
9140         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9141         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9142     }
9143     if (appData.debugMode) {
9144       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9145               result, resultDetails ? resultDetails : "(null)", whosays);
9146     }
9147
9148     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9149
9150     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9151         /* If we are playing on ICS, the server decides when the
9152            game is over, but the engine can offer to draw, claim
9153            a draw, or resign.
9154          */
9155 #if ZIPPY
9156         if (appData.zippyPlay && first.initDone) {
9157             if (result == GameIsDrawn) {
9158                 /* In case draw still needs to be claimed */
9159                 SendToICS(ics_prefix);
9160                 SendToICS("draw\n");
9161             } else if (StrCaseStr(resultDetails, "resign")) {
9162                 SendToICS(ics_prefix);
9163                 SendToICS("resign\n");
9164             }
9165         }
9166 #endif
9167         endingGame = 0; /* [HGM] crash */
9168         return;
9169     }
9170
9171     /* If we're loading the game from a file, stop */
9172     if (whosays == GE_FILE) {
9173       (void) StopLoadGameTimer();
9174       gameFileFP = NULL;
9175     }
9176
9177     /* Cancel draw offers */
9178     first.offeredDraw = second.offeredDraw = 0;
9179
9180     /* If this is an ICS game, only ICS can really say it's done;
9181        if not, anyone can. */
9182     isIcsGame = (gameMode == IcsPlayingWhite ||
9183                  gameMode == IcsPlayingBlack ||
9184                  gameMode == IcsObserving    ||
9185                  gameMode == IcsExamining);
9186
9187     if (!isIcsGame || whosays == GE_ICS) {
9188         /* OK -- not an ICS game, or ICS said it was done */
9189         StopClocks();
9190         if (!isIcsGame && !appData.noChessProgram)
9191           SetUserThinkingEnables();
9192
9193         /* [HGM] if a machine claims the game end we verify this claim */
9194         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9195             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9196                 char claimer;
9197                 ChessMove trueResult = (ChessMove) -1;
9198
9199                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9200                                             first.twoMachinesColor[0] :
9201                                             second.twoMachinesColor[0] ;
9202
9203                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9204                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9205                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9206                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9207                 } else
9208                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9209                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9210                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9211                 } else
9212                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9213                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9214                 }
9215
9216                 // now verify win claims, but not in drop games, as we don't understand those yet
9217                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9218                                                  || gameInfo.variant == VariantGreat) &&
9219                     (result == WhiteWins && claimer == 'w' ||
9220                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9221                       if (appData.debugMode) {
9222                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9223                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9224                       }
9225                       if(result != trueResult) {
9226                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9227                               result = claimer == 'w' ? BlackWins : WhiteWins;
9228                               resultDetails = buf;
9229                       }
9230                 } else
9231                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9232                     && (forwardMostMove <= backwardMostMove ||
9233                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9234                         (claimer=='b')==(forwardMostMove&1))
9235                                                                                   ) {
9236                       /* [HGM] verify: draws that were not flagged are false claims */
9237                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9238                       result = claimer == 'w' ? BlackWins : WhiteWins;
9239                       resultDetails = buf;
9240                 }
9241                 /* (Claiming a loss is accepted no questions asked!) */
9242             }
9243             /* [HGM] bare: don't allow bare King to win */
9244             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9245                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9246                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9247                && result != GameIsDrawn)
9248             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9249                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9250                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9251                         if(p >= 0 && p <= (int)WhiteKing) k++;
9252                 }
9253                 if (appData.debugMode) {
9254                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9255                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9256                 }
9257                 if(k <= 1) {
9258                         result = GameIsDrawn;
9259                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9260                         resultDetails = buf;
9261                 }
9262             }
9263         }
9264
9265
9266         if(serverMoves != NULL && !loadFlag) { char c = '=';
9267             if(result==WhiteWins) c = '+';
9268             if(result==BlackWins) c = '-';
9269             if(resultDetails != NULL)
9270                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9271         }
9272         if (resultDetails != NULL) {
9273             gameInfo.result = result;
9274             gameInfo.resultDetails = StrSave(resultDetails);
9275
9276             /* display last move only if game was not loaded from file */
9277             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9278                 DisplayMove(currentMove - 1);
9279
9280             if (forwardMostMove != 0) {
9281                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9282                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9283                                                                 ) {
9284                     if (*appData.saveGameFile != NULLCHAR) {
9285                         SaveGameToFile(appData.saveGameFile, TRUE);
9286                     } else if (appData.autoSaveGames) {
9287                         AutoSaveGame();
9288                     }
9289                     if (*appData.savePositionFile != NULLCHAR) {
9290                         SavePositionToFile(appData.savePositionFile);
9291                     }
9292                 }
9293             }
9294
9295             /* Tell program how game ended in case it is learning */
9296             /* [HGM] Moved this to after saving the PGN, just in case */
9297             /* engine died and we got here through time loss. In that */
9298             /* case we will get a fatal error writing the pipe, which */
9299             /* would otherwise lose us the PGN.                       */
9300             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9301             /* output during GameEnds should never be fatal anymore   */
9302             if (gameMode == MachinePlaysWhite ||
9303                 gameMode == MachinePlaysBlack ||
9304                 gameMode == TwoMachinesPlay ||
9305                 gameMode == IcsPlayingWhite ||
9306                 gameMode == IcsPlayingBlack ||
9307                 gameMode == BeginningOfGame) {
9308                 char buf[MSG_SIZ];
9309                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9310                         resultDetails);
9311                 if (first.pr != NoProc) {
9312                     SendToProgram(buf, &first);
9313                 }
9314                 if (second.pr != NoProc &&
9315                     gameMode == TwoMachinesPlay) {
9316                     SendToProgram(buf, &second);
9317                 }
9318             }
9319         }
9320
9321         if (appData.icsActive) {
9322             if (appData.quietPlay &&
9323                 (gameMode == IcsPlayingWhite ||
9324                  gameMode == IcsPlayingBlack)) {
9325                 SendToICS(ics_prefix);
9326                 SendToICS("set shout 1\n");
9327             }
9328             nextGameMode = IcsIdle;
9329             ics_user_moved = FALSE;
9330             /* clean up premove.  It's ugly when the game has ended and the
9331              * premove highlights are still on the board.
9332              */
9333             if (gotPremove) {
9334               gotPremove = FALSE;
9335               ClearPremoveHighlights();
9336               DrawPosition(FALSE, boards[currentMove]);
9337             }
9338             if (whosays == GE_ICS) {
9339                 switch (result) {
9340                 case WhiteWins:
9341                     if (gameMode == IcsPlayingWhite)
9342                         PlayIcsWinSound();
9343                     else if(gameMode == IcsPlayingBlack)
9344                         PlayIcsLossSound();
9345                     break;
9346                 case BlackWins:
9347                     if (gameMode == IcsPlayingBlack)
9348                         PlayIcsWinSound();
9349                     else if(gameMode == IcsPlayingWhite)
9350                         PlayIcsLossSound();
9351                     break;
9352                 case GameIsDrawn:
9353                     PlayIcsDrawSound();
9354                     break;
9355                 default:
9356                     PlayIcsUnfinishedSound();
9357                 }
9358             }
9359         } else if (gameMode == EditGame ||
9360                    gameMode == PlayFromGameFile ||
9361                    gameMode == AnalyzeMode ||
9362                    gameMode == AnalyzeFile) {
9363             nextGameMode = gameMode;
9364         } else {
9365             nextGameMode = EndOfGame;
9366         }
9367         pausing = FALSE;
9368         ModeHighlight();
9369     } else {
9370         nextGameMode = gameMode;
9371     }
9372
9373     if (appData.noChessProgram) {
9374         gameMode = nextGameMode;
9375         ModeHighlight();
9376         endingGame = 0; /* [HGM] crash */
9377         return;
9378     }
9379
9380     if (first.reuse) {
9381         /* Put first chess program into idle state */
9382         if (first.pr != NoProc &&
9383             (gameMode == MachinePlaysWhite ||
9384              gameMode == MachinePlaysBlack ||
9385              gameMode == TwoMachinesPlay ||
9386              gameMode == IcsPlayingWhite ||
9387              gameMode == IcsPlayingBlack ||
9388              gameMode == BeginningOfGame)) {
9389             SendToProgram("force\n", &first);
9390             if (first.usePing) {
9391               char buf[MSG_SIZ];
9392               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9393               SendToProgram(buf, &first);
9394             }
9395         }
9396     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9397         /* Kill off first chess program */
9398         if (first.isr != NULL)
9399           RemoveInputSource(first.isr);
9400         first.isr = NULL;
9401
9402         if (first.pr != NoProc) {
9403             ExitAnalyzeMode();
9404             DoSleep( appData.delayBeforeQuit );
9405             SendToProgram("quit\n", &first);
9406             DoSleep( appData.delayAfterQuit );
9407             DestroyChildProcess(first.pr, first.useSigterm);
9408         }
9409         first.pr = NoProc;
9410     }
9411     if (second.reuse) {
9412         /* Put second chess program into idle state */
9413         if (second.pr != NoProc &&
9414             gameMode == TwoMachinesPlay) {
9415             SendToProgram("force\n", &second);
9416             if (second.usePing) {
9417               char buf[MSG_SIZ];
9418               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9419               SendToProgram(buf, &second);
9420             }
9421         }
9422     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9423         /* Kill off second chess program */
9424         if (second.isr != NULL)
9425           RemoveInputSource(second.isr);
9426         second.isr = NULL;
9427
9428         if (second.pr != NoProc) {
9429             DoSleep( appData.delayBeforeQuit );
9430             SendToProgram("quit\n", &second);
9431             DoSleep( appData.delayAfterQuit );
9432             DestroyChildProcess(second.pr, second.useSigterm);
9433         }
9434         second.pr = NoProc;
9435     }
9436
9437     if (matchMode && gameMode == TwoMachinesPlay) {
9438         switch (result) {
9439         case WhiteWins:
9440           if (first.twoMachinesColor[0] == 'w') {
9441             first.matchWins++;
9442           } else {
9443             second.matchWins++;
9444           }
9445           break;
9446         case BlackWins:
9447           if (first.twoMachinesColor[0] == 'b') {
9448             first.matchWins++;
9449           } else {
9450             second.matchWins++;
9451           }
9452           break;
9453         default:
9454           break;
9455         }
9456         if (matchGame < appData.matchGames) {
9457             char *tmp;
9458             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9459                 tmp = first.twoMachinesColor;
9460                 first.twoMachinesColor = second.twoMachinesColor;
9461                 second.twoMachinesColor = tmp;
9462             }
9463             gameMode = nextGameMode;
9464             matchGame++;
9465             if(appData.matchPause>10000 || appData.matchPause<10)
9466                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9467             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9468             endingGame = 0; /* [HGM] crash */
9469             return;
9470         } else {
9471             gameMode = nextGameMode;
9472             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9473                      first.tidy, second.tidy,
9474                      first.matchWins, second.matchWins,
9475                      appData.matchGames - (first.matchWins + second.matchWins));
9476             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9477             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9478                 first.twoMachinesColor = "black\n";
9479                 second.twoMachinesColor = "white\n";
9480             } else {
9481                 first.twoMachinesColor = "white\n";
9482                 second.twoMachinesColor = "black\n";
9483             }
9484         }
9485     }
9486     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9487         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9488       ExitAnalyzeMode();
9489     gameMode = nextGameMode;
9490     ModeHighlight();
9491     endingGame = 0;  /* [HGM] crash */
9492     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9493       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9494         matchMode = FALSE; appData.matchGames = matchGame = 0;
9495         DisplayNote(buf);
9496       }
9497     }
9498 }
9499
9500 /* Assumes program was just initialized (initString sent).
9501    Leaves program in force mode. */
9502 void
9503 FeedMovesToProgram(cps, upto)
9504      ChessProgramState *cps;
9505      int upto;
9506 {
9507     int i;
9508
9509     if (appData.debugMode)
9510       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9511               startedFromSetupPosition ? "position and " : "",
9512               backwardMostMove, upto, cps->which);
9513     if(currentlyInitializedVariant != gameInfo.variant) {
9514       char buf[MSG_SIZ];
9515         // [HGM] variantswitch: make engine aware of new variant
9516         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9517                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9518         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9519         SendToProgram(buf, cps);
9520         currentlyInitializedVariant = gameInfo.variant;
9521     }
9522     SendToProgram("force\n", cps);
9523     if (startedFromSetupPosition) {
9524         SendBoard(cps, backwardMostMove);
9525     if (appData.debugMode) {
9526         fprintf(debugFP, "feedMoves\n");
9527     }
9528     }
9529     for (i = backwardMostMove; i < upto; i++) {
9530         SendMoveToProgram(i, cps);
9531     }
9532 }
9533
9534
9535 void
9536 ResurrectChessProgram()
9537 {
9538      /* The chess program may have exited.
9539         If so, restart it and feed it all the moves made so far. */
9540
9541     if (appData.noChessProgram || first.pr != NoProc) return;
9542
9543     StartChessProgram(&first);
9544     InitChessProgram(&first, FALSE);
9545     FeedMovesToProgram(&first, currentMove);
9546
9547     if (!first.sendTime) {
9548         /* can't tell gnuchess what its clock should read,
9549            so we bow to its notion. */
9550         ResetClocks();
9551         timeRemaining[0][currentMove] = whiteTimeRemaining;
9552         timeRemaining[1][currentMove] = blackTimeRemaining;
9553     }
9554
9555     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9556                 appData.icsEngineAnalyze) && first.analysisSupport) {
9557       SendToProgram("analyze\n", &first);
9558       first.analyzing = TRUE;
9559     }
9560 }
9561
9562 /*
9563  * Button procedures
9564  */
9565 void
9566 Reset(redraw, init)
9567      int redraw, init;
9568 {
9569     int i;
9570
9571     if (appData.debugMode) {
9572         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9573                 redraw, init, gameMode);
9574     }
9575     CleanupTail(); // [HGM] vari: delete any stored variations
9576     pausing = pauseExamInvalid = FALSE;
9577     startedFromSetupPosition = blackPlaysFirst = FALSE;
9578     firstMove = TRUE;
9579     whiteFlag = blackFlag = FALSE;
9580     userOfferedDraw = FALSE;
9581     hintRequested = bookRequested = FALSE;
9582     first.maybeThinking = FALSE;
9583     second.maybeThinking = FALSE;
9584     first.bookSuspend = FALSE; // [HGM] book
9585     second.bookSuspend = FALSE;
9586     thinkOutput[0] = NULLCHAR;
9587     lastHint[0] = NULLCHAR;
9588     ClearGameInfo(&gameInfo);
9589     gameInfo.variant = StringToVariant(appData.variant);
9590     ics_user_moved = ics_clock_paused = FALSE;
9591     ics_getting_history = H_FALSE;
9592     ics_gamenum = -1;
9593     white_holding[0] = black_holding[0] = NULLCHAR;
9594     ClearProgramStats();
9595     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9596
9597     ResetFrontEnd();
9598     ClearHighlights();
9599     flipView = appData.flipView;
9600     ClearPremoveHighlights();
9601     gotPremove = FALSE;
9602     alarmSounded = FALSE;
9603
9604     GameEnds(EndOfFile, NULL, GE_PLAYER);
9605     if(appData.serverMovesName != NULL) {
9606         /* [HGM] prepare to make moves file for broadcasting */
9607         clock_t t = clock();
9608         if(serverMoves != NULL) fclose(serverMoves);
9609         serverMoves = fopen(appData.serverMovesName, "r");
9610         if(serverMoves != NULL) {
9611             fclose(serverMoves);
9612             /* delay 15 sec before overwriting, so all clients can see end */
9613             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9614         }
9615         serverMoves = fopen(appData.serverMovesName, "w");
9616     }
9617
9618     ExitAnalyzeMode();
9619     gameMode = BeginningOfGame;
9620     ModeHighlight();
9621     if(appData.icsActive) gameInfo.variant = VariantNormal;
9622     currentMove = forwardMostMove = backwardMostMove = 0;
9623     InitPosition(redraw);
9624     for (i = 0; i < MAX_MOVES; i++) {
9625         if (commentList[i] != NULL) {
9626             free(commentList[i]);
9627             commentList[i] = NULL;
9628         }
9629     }
9630     ResetClocks();
9631     timeRemaining[0][0] = whiteTimeRemaining;
9632     timeRemaining[1][0] = blackTimeRemaining;
9633     if (first.pr == NULL) {
9634         StartChessProgram(&first);
9635     }
9636     if (init) {
9637             InitChessProgram(&first, startedFromSetupPosition);
9638     }
9639     DisplayTitle("");
9640     DisplayMessage("", "");
9641     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9642     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9643 }
9644
9645 void
9646 AutoPlayGameLoop()
9647 {
9648     for (;;) {
9649         if (!AutoPlayOneMove())
9650           return;
9651         if (matchMode || appData.timeDelay == 0)
9652           continue;
9653         if (appData.timeDelay < 0)
9654           return;
9655         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9656         break;
9657     }
9658 }
9659
9660
9661 int
9662 AutoPlayOneMove()
9663 {
9664     int fromX, fromY, toX, toY;
9665
9666     if (appData.debugMode) {
9667       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9668     }
9669
9670     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9671       return FALSE;
9672
9673     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9674       pvInfoList[currentMove].depth = programStats.depth;
9675       pvInfoList[currentMove].score = programStats.score;
9676       pvInfoList[currentMove].time  = 0;
9677       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9678     }
9679
9680     if (currentMove >= forwardMostMove) {
9681       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9682       gameMode = EditGame;
9683       ModeHighlight();
9684
9685       /* [AS] Clear current move marker at the end of a game */
9686       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9687
9688       return FALSE;
9689     }
9690
9691     toX = moveList[currentMove][2] - AAA;
9692     toY = moveList[currentMove][3] - ONE;
9693
9694     if (moveList[currentMove][1] == '@') {
9695         if (appData.highlightLastMove) {
9696             SetHighlights(-1, -1, toX, toY);
9697         }
9698     } else {
9699         fromX = moveList[currentMove][0] - AAA;
9700         fromY = moveList[currentMove][1] - ONE;
9701
9702         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9703
9704         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9705
9706         if (appData.highlightLastMove) {
9707             SetHighlights(fromX, fromY, toX, toY);
9708         }
9709     }
9710     DisplayMove(currentMove);
9711     SendMoveToProgram(currentMove++, &first);
9712     DisplayBothClocks();
9713     DrawPosition(FALSE, boards[currentMove]);
9714     // [HGM] PV info: always display, routine tests if empty
9715     DisplayComment(currentMove - 1, commentList[currentMove]);
9716     return TRUE;
9717 }
9718
9719
9720 int
9721 LoadGameOneMove(readAhead)
9722      ChessMove readAhead;
9723 {
9724     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9725     char promoChar = NULLCHAR;
9726     ChessMove moveType;
9727     char move[MSG_SIZ];
9728     char *p, *q;
9729
9730     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9731         gameMode != AnalyzeMode && gameMode != Training) {
9732         gameFileFP = NULL;
9733         return FALSE;
9734     }
9735
9736     yyboardindex = forwardMostMove;
9737     if (readAhead != EndOfFile) {
9738       moveType = readAhead;
9739     } else {
9740       if (gameFileFP == NULL)
9741           return FALSE;
9742       moveType = (ChessMove) Myylex();
9743     }
9744
9745     done = FALSE;
9746     switch (moveType) {
9747       case Comment:
9748         if (appData.debugMode)
9749           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9750         p = yy_text;
9751
9752         /* append the comment but don't display it */
9753         AppendComment(currentMove, p, FALSE);
9754         return TRUE;
9755
9756       case WhiteCapturesEnPassant:
9757       case BlackCapturesEnPassant:
9758       case WhitePromotion:
9759       case BlackPromotion:
9760       case WhiteNonPromotion:
9761       case BlackNonPromotion:
9762       case NormalMove:
9763       case WhiteKingSideCastle:
9764       case WhiteQueenSideCastle:
9765       case BlackKingSideCastle:
9766       case BlackQueenSideCastle:
9767       case WhiteKingSideCastleWild:
9768       case WhiteQueenSideCastleWild:
9769       case BlackKingSideCastleWild:
9770       case BlackQueenSideCastleWild:
9771       /* PUSH Fabien */
9772       case WhiteHSideCastleFR:
9773       case WhiteASideCastleFR:
9774       case BlackHSideCastleFR:
9775       case BlackASideCastleFR:
9776       /* POP Fabien */
9777         if (appData.debugMode)
9778           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9779         fromX = currentMoveString[0] - AAA;
9780         fromY = currentMoveString[1] - ONE;
9781         toX = currentMoveString[2] - AAA;
9782         toY = currentMoveString[3] - ONE;
9783         promoChar = currentMoveString[4];
9784         break;
9785
9786       case WhiteDrop:
9787       case BlackDrop:
9788         if (appData.debugMode)
9789           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9790         fromX = moveType == WhiteDrop ?
9791           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9792         (int) CharToPiece(ToLower(currentMoveString[0]));
9793         fromY = DROP_RANK;
9794         toX = currentMoveString[2] - AAA;
9795         toY = currentMoveString[3] - ONE;
9796         break;
9797
9798       case WhiteWins:
9799       case BlackWins:
9800       case GameIsDrawn:
9801       case GameUnfinished:
9802         if (appData.debugMode)
9803           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9804         p = strchr(yy_text, '{');
9805         if (p == NULL) p = strchr(yy_text, '(');
9806         if (p == NULL) {
9807             p = yy_text;
9808             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9809         } else {
9810             q = strchr(p, *p == '{' ? '}' : ')');
9811             if (q != NULL) *q = NULLCHAR;
9812             p++;
9813         }
9814         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9815         GameEnds(moveType, p, GE_FILE);
9816         done = TRUE;
9817         if (cmailMsgLoaded) {
9818             ClearHighlights();
9819             flipView = WhiteOnMove(currentMove);
9820             if (moveType == GameUnfinished) flipView = !flipView;
9821             if (appData.debugMode)
9822               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9823         }
9824         break;
9825
9826       case EndOfFile:
9827         if (appData.debugMode)
9828           fprintf(debugFP, "Parser hit end of file\n");
9829         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9830           case MT_NONE:
9831           case MT_CHECK:
9832             break;
9833           case MT_CHECKMATE:
9834           case MT_STAINMATE:
9835             if (WhiteOnMove(currentMove)) {
9836                 GameEnds(BlackWins, "Black mates", GE_FILE);
9837             } else {
9838                 GameEnds(WhiteWins, "White mates", GE_FILE);
9839             }
9840             break;
9841           case MT_STALEMATE:
9842             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9843             break;
9844         }
9845         done = TRUE;
9846         break;
9847
9848       case MoveNumberOne:
9849         if (lastLoadGameStart == GNUChessGame) {
9850             /* GNUChessGames have numbers, but they aren't move numbers */
9851             if (appData.debugMode)
9852               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9853                       yy_text, (int) moveType);
9854             return LoadGameOneMove(EndOfFile); /* tail recursion */
9855         }
9856         /* else fall thru */
9857
9858       case XBoardGame:
9859       case GNUChessGame:
9860       case PGNTag:
9861         /* Reached start of next game in file */
9862         if (appData.debugMode)
9863           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9864         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9865           case MT_NONE:
9866           case MT_CHECK:
9867             break;
9868           case MT_CHECKMATE:
9869           case MT_STAINMATE:
9870             if (WhiteOnMove(currentMove)) {
9871                 GameEnds(BlackWins, "Black mates", GE_FILE);
9872             } else {
9873                 GameEnds(WhiteWins, "White mates", GE_FILE);
9874             }
9875             break;
9876           case MT_STALEMATE:
9877             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9878             break;
9879         }
9880         done = TRUE;
9881         break;
9882
9883       case PositionDiagram:     /* should not happen; ignore */
9884       case ElapsedTime:         /* ignore */
9885       case NAG:                 /* ignore */
9886         if (appData.debugMode)
9887           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9888                   yy_text, (int) moveType);
9889         return LoadGameOneMove(EndOfFile); /* tail recursion */
9890
9891       case IllegalMove:
9892         if (appData.testLegality) {
9893             if (appData.debugMode)
9894               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9895             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9896                     (forwardMostMove / 2) + 1,
9897                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9898             DisplayError(move, 0);
9899             done = TRUE;
9900         } else {
9901             if (appData.debugMode)
9902               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9903                       yy_text, currentMoveString);
9904             fromX = currentMoveString[0] - AAA;
9905             fromY = currentMoveString[1] - ONE;
9906             toX = currentMoveString[2] - AAA;
9907             toY = currentMoveString[3] - ONE;
9908             promoChar = currentMoveString[4];
9909         }
9910         break;
9911
9912       case AmbiguousMove:
9913         if (appData.debugMode)
9914           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9915         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9916                 (forwardMostMove / 2) + 1,
9917                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9918         DisplayError(move, 0);
9919         done = TRUE;
9920         break;
9921
9922       default:
9923       case ImpossibleMove:
9924         if (appData.debugMode)
9925           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9926         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9927                 (forwardMostMove / 2) + 1,
9928                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9929         DisplayError(move, 0);
9930         done = TRUE;
9931         break;
9932     }
9933
9934     if (done) {
9935         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9936             DrawPosition(FALSE, boards[currentMove]);
9937             DisplayBothClocks();
9938             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9939               DisplayComment(currentMove - 1, commentList[currentMove]);
9940         }
9941         (void) StopLoadGameTimer();
9942         gameFileFP = NULL;
9943         cmailOldMove = forwardMostMove;
9944         return FALSE;
9945     } else {
9946         /* currentMoveString is set as a side-effect of yylex */
9947
9948         thinkOutput[0] = NULLCHAR;
9949         MakeMove(fromX, fromY, toX, toY, promoChar);
9950         currentMove = forwardMostMove;
9951         return TRUE;
9952     }
9953 }
9954
9955 /* Load the nth game from the given file */
9956 int
9957 LoadGameFromFile(filename, n, title, useList)
9958      char *filename;
9959      int n;
9960      char *title;
9961      /*Boolean*/ int useList;
9962 {
9963     FILE *f;
9964     char buf[MSG_SIZ];
9965
9966     if (strcmp(filename, "-") == 0) {
9967         f = stdin;
9968         title = "stdin";
9969     } else {
9970         f = fopen(filename, "rb");
9971         if (f == NULL) {
9972           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9973             DisplayError(buf, errno);
9974             return FALSE;
9975         }
9976     }
9977     if (fseek(f, 0, 0) == -1) {
9978         /* f is not seekable; probably a pipe */
9979         useList = FALSE;
9980     }
9981     if (useList && n == 0) {
9982         int error = GameListBuild(f);
9983         if (error) {
9984             DisplayError(_("Cannot build game list"), error);
9985         } else if (!ListEmpty(&gameList) &&
9986                    ((ListGame *) gameList.tailPred)->number > 1) {
9987             GameListPopUp(f, title);
9988             return TRUE;
9989         }
9990         GameListDestroy();
9991         n = 1;
9992     }
9993     if (n == 0) n = 1;
9994     return LoadGame(f, n, title, FALSE);
9995 }
9996
9997
9998 void
9999 MakeRegisteredMove()
10000 {
10001     int fromX, fromY, toX, toY;
10002     char promoChar;
10003     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10004         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10005           case CMAIL_MOVE:
10006           case CMAIL_DRAW:
10007             if (appData.debugMode)
10008               fprintf(debugFP, "Restoring %s for game %d\n",
10009                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10010
10011             thinkOutput[0] = NULLCHAR;
10012             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10013             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10014             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10015             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10016             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10017             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10018             MakeMove(fromX, fromY, toX, toY, promoChar);
10019             ShowMove(fromX, fromY, toX, toY);
10020
10021             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10022               case MT_NONE:
10023               case MT_CHECK:
10024                 break;
10025
10026               case MT_CHECKMATE:
10027               case MT_STAINMATE:
10028                 if (WhiteOnMove(currentMove)) {
10029                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10030                 } else {
10031                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10032                 }
10033                 break;
10034
10035               case MT_STALEMATE:
10036                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10037                 break;
10038             }
10039
10040             break;
10041
10042           case CMAIL_RESIGN:
10043             if (WhiteOnMove(currentMove)) {
10044                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10045             } else {
10046                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10047             }
10048             break;
10049
10050           case CMAIL_ACCEPT:
10051             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10052             break;
10053
10054           default:
10055             break;
10056         }
10057     }
10058
10059     return;
10060 }
10061
10062 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10063 int
10064 CmailLoadGame(f, gameNumber, title, useList)
10065      FILE *f;
10066      int gameNumber;
10067      char *title;
10068      int useList;
10069 {
10070     int retVal;
10071
10072     if (gameNumber > nCmailGames) {
10073         DisplayError(_("No more games in this message"), 0);
10074         return FALSE;
10075     }
10076     if (f == lastLoadGameFP) {
10077         int offset = gameNumber - lastLoadGameNumber;
10078         if (offset == 0) {
10079             cmailMsg[0] = NULLCHAR;
10080             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10081                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10082                 nCmailMovesRegistered--;
10083             }
10084             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10085             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10086                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10087             }
10088         } else {
10089             if (! RegisterMove()) return FALSE;
10090         }
10091     }
10092
10093     retVal = LoadGame(f, gameNumber, title, useList);
10094
10095     /* Make move registered during previous look at this game, if any */
10096     MakeRegisteredMove();
10097
10098     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10099         commentList[currentMove]
10100           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10101         DisplayComment(currentMove - 1, commentList[currentMove]);
10102     }
10103
10104     return retVal;
10105 }
10106
10107 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10108 int
10109 ReloadGame(offset)
10110      int offset;
10111 {
10112     int gameNumber = lastLoadGameNumber + offset;
10113     if (lastLoadGameFP == NULL) {
10114         DisplayError(_("No game has been loaded yet"), 0);
10115         return FALSE;
10116     }
10117     if (gameNumber <= 0) {
10118         DisplayError(_("Can't back up any further"), 0);
10119         return FALSE;
10120     }
10121     if (cmailMsgLoaded) {
10122         return CmailLoadGame(lastLoadGameFP, gameNumber,
10123                              lastLoadGameTitle, lastLoadGameUseList);
10124     } else {
10125         return LoadGame(lastLoadGameFP, gameNumber,
10126                         lastLoadGameTitle, lastLoadGameUseList);
10127     }
10128 }
10129
10130
10131
10132 /* Load the nth game from open file f */
10133 int
10134 LoadGame(f, gameNumber, title, useList)
10135      FILE *f;
10136      int gameNumber;
10137      char *title;
10138      int useList;
10139 {
10140     ChessMove cm;
10141     char buf[MSG_SIZ];
10142     int gn = gameNumber;
10143     ListGame *lg = NULL;
10144     int numPGNTags = 0;
10145     int err;
10146     GameMode oldGameMode;
10147     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10148
10149     if (appData.debugMode)
10150         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10151
10152     if (gameMode == Training )
10153         SetTrainingModeOff();
10154
10155     oldGameMode = gameMode;
10156     if (gameMode != BeginningOfGame) {
10157       Reset(FALSE, TRUE);
10158     }
10159
10160     gameFileFP = f;
10161     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10162         fclose(lastLoadGameFP);
10163     }
10164
10165     if (useList) {
10166         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10167
10168         if (lg) {
10169             fseek(f, lg->offset, 0);
10170             GameListHighlight(gameNumber);
10171             gn = 1;
10172         }
10173         else {
10174             DisplayError(_("Game number out of range"), 0);
10175             return FALSE;
10176         }
10177     } else {
10178         GameListDestroy();
10179         if (fseek(f, 0, 0) == -1) {
10180             if (f == lastLoadGameFP ?
10181                 gameNumber == lastLoadGameNumber + 1 :
10182                 gameNumber == 1) {
10183                 gn = 1;
10184             } else {
10185                 DisplayError(_("Can't seek on game file"), 0);
10186                 return FALSE;
10187             }
10188         }
10189     }
10190     lastLoadGameFP = f;
10191     lastLoadGameNumber = gameNumber;
10192     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10193     lastLoadGameUseList = useList;
10194
10195     yynewfile(f);
10196
10197     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10198       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10199                 lg->gameInfo.black);
10200             DisplayTitle(buf);
10201     } else if (*title != NULLCHAR) {
10202         if (gameNumber > 1) {
10203           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10204             DisplayTitle(buf);
10205         } else {
10206             DisplayTitle(title);
10207         }
10208     }
10209
10210     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10211         gameMode = PlayFromGameFile;
10212         ModeHighlight();
10213     }
10214
10215     currentMove = forwardMostMove = backwardMostMove = 0;
10216     CopyBoard(boards[0], initialPosition);
10217     StopClocks();
10218
10219     /*
10220      * Skip the first gn-1 games in the file.
10221      * Also skip over anything that precedes an identifiable
10222      * start of game marker, to avoid being confused by
10223      * garbage at the start of the file.  Currently
10224      * recognized start of game markers are the move number "1",
10225      * the pattern "gnuchess .* game", the pattern
10226      * "^[#;%] [^ ]* game file", and a PGN tag block.
10227      * A game that starts with one of the latter two patterns
10228      * will also have a move number 1, possibly
10229      * following a position diagram.
10230      * 5-4-02: Let's try being more lenient and allowing a game to
10231      * start with an unnumbered move.  Does that break anything?
10232      */
10233     cm = lastLoadGameStart = EndOfFile;
10234     while (gn > 0) {
10235         yyboardindex = forwardMostMove;
10236         cm = (ChessMove) Myylex();
10237         switch (cm) {
10238           case EndOfFile:
10239             if (cmailMsgLoaded) {
10240                 nCmailGames = CMAIL_MAX_GAMES - gn;
10241             } else {
10242                 Reset(TRUE, TRUE);
10243                 DisplayError(_("Game not found in file"), 0);
10244             }
10245             return FALSE;
10246
10247           case GNUChessGame:
10248           case XBoardGame:
10249             gn--;
10250             lastLoadGameStart = cm;
10251             break;
10252
10253           case MoveNumberOne:
10254             switch (lastLoadGameStart) {
10255               case GNUChessGame:
10256               case XBoardGame:
10257               case PGNTag:
10258                 break;
10259               case MoveNumberOne:
10260               case EndOfFile:
10261                 gn--;           /* count this game */
10262                 lastLoadGameStart = cm;
10263                 break;
10264               default:
10265                 /* impossible */
10266                 break;
10267             }
10268             break;
10269
10270           case PGNTag:
10271             switch (lastLoadGameStart) {
10272               case GNUChessGame:
10273               case PGNTag:
10274               case MoveNumberOne:
10275               case EndOfFile:
10276                 gn--;           /* count this game */
10277                 lastLoadGameStart = cm;
10278                 break;
10279               case XBoardGame:
10280                 lastLoadGameStart = cm; /* game counted already */
10281                 break;
10282               default:
10283                 /* impossible */
10284                 break;
10285             }
10286             if (gn > 0) {
10287                 do {
10288                     yyboardindex = forwardMostMove;
10289                     cm = (ChessMove) Myylex();
10290                 } while (cm == PGNTag || cm == Comment);
10291             }
10292             break;
10293
10294           case WhiteWins:
10295           case BlackWins:
10296           case GameIsDrawn:
10297             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10298                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10299                     != CMAIL_OLD_RESULT) {
10300                     nCmailResults ++ ;
10301                     cmailResult[  CMAIL_MAX_GAMES
10302                                 - gn - 1] = CMAIL_OLD_RESULT;
10303                 }
10304             }
10305             break;
10306
10307           case NormalMove:
10308             /* Only a NormalMove can be at the start of a game
10309              * without a position diagram. */
10310             if (lastLoadGameStart == EndOfFile ) {
10311               gn--;
10312               lastLoadGameStart = MoveNumberOne;
10313             }
10314             break;
10315
10316           default:
10317             break;
10318         }
10319     }
10320
10321     if (appData.debugMode)
10322       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10323
10324     if (cm == XBoardGame) {
10325         /* Skip any header junk before position diagram and/or move 1 */
10326         for (;;) {
10327             yyboardindex = forwardMostMove;
10328             cm = (ChessMove) Myylex();
10329
10330             if (cm == EndOfFile ||
10331                 cm == GNUChessGame || cm == XBoardGame) {
10332                 /* Empty game; pretend end-of-file and handle later */
10333                 cm = EndOfFile;
10334                 break;
10335             }
10336
10337             if (cm == MoveNumberOne || cm == PositionDiagram ||
10338                 cm == PGNTag || cm == Comment)
10339               break;
10340         }
10341     } else if (cm == GNUChessGame) {
10342         if (gameInfo.event != NULL) {
10343             free(gameInfo.event);
10344         }
10345         gameInfo.event = StrSave(yy_text);
10346     }
10347
10348     startedFromSetupPosition = FALSE;
10349     while (cm == PGNTag) {
10350         if (appData.debugMode)
10351           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10352         err = ParsePGNTag(yy_text, &gameInfo);
10353         if (!err) numPGNTags++;
10354
10355         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10356         if(gameInfo.variant != oldVariant) {
10357             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10358             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10359             InitPosition(TRUE);
10360             oldVariant = gameInfo.variant;
10361             if (appData.debugMode)
10362               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10363         }
10364
10365
10366         if (gameInfo.fen != NULL) {
10367           Board initial_position;
10368           startedFromSetupPosition = TRUE;
10369           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10370             Reset(TRUE, TRUE);
10371             DisplayError(_("Bad FEN position in file"), 0);
10372             return FALSE;
10373           }
10374           CopyBoard(boards[0], initial_position);
10375           if (blackPlaysFirst) {
10376             currentMove = forwardMostMove = backwardMostMove = 1;
10377             CopyBoard(boards[1], initial_position);
10378             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10379             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10380             timeRemaining[0][1] = whiteTimeRemaining;
10381             timeRemaining[1][1] = blackTimeRemaining;
10382             if (commentList[0] != NULL) {
10383               commentList[1] = commentList[0];
10384               commentList[0] = NULL;
10385             }
10386           } else {
10387             currentMove = forwardMostMove = backwardMostMove = 0;
10388           }
10389           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10390           {   int i;
10391               initialRulePlies = FENrulePlies;
10392               for( i=0; i< nrCastlingRights; i++ )
10393                   initialRights[i] = initial_position[CASTLING][i];
10394           }
10395           yyboardindex = forwardMostMove;
10396           free(gameInfo.fen);
10397           gameInfo.fen = NULL;
10398         }
10399
10400         yyboardindex = forwardMostMove;
10401         cm = (ChessMove) Myylex();
10402
10403         /* Handle comments interspersed among the tags */
10404         while (cm == Comment) {
10405             char *p;
10406             if (appData.debugMode)
10407               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10408             p = yy_text;
10409             AppendComment(currentMove, p, FALSE);
10410             yyboardindex = forwardMostMove;
10411             cm = (ChessMove) Myylex();
10412         }
10413     }
10414
10415     /* don't rely on existence of Event tag since if game was
10416      * pasted from clipboard the Event tag may not exist
10417      */
10418     if (numPGNTags > 0){
10419         char *tags;
10420         if (gameInfo.variant == VariantNormal) {
10421           VariantClass v = StringToVariant(gameInfo.event);
10422           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10423           if(v < VariantShogi) gameInfo.variant = v;
10424         }
10425         if (!matchMode) {
10426           if( appData.autoDisplayTags ) {
10427             tags = PGNTags(&gameInfo);
10428             TagsPopUp(tags, CmailMsg());
10429             free(tags);
10430           }
10431         }
10432     } else {
10433         /* Make something up, but don't display it now */
10434         SetGameInfo();
10435         TagsPopDown();
10436     }
10437
10438     if (cm == PositionDiagram) {
10439         int i, j;
10440         char *p;
10441         Board initial_position;
10442
10443         if (appData.debugMode)
10444           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10445
10446         if (!startedFromSetupPosition) {
10447             p = yy_text;
10448             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10449               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10450                 switch (*p) {
10451                   case '{':
10452                   case '[':
10453                   case '-':
10454                   case ' ':
10455                   case '\t':
10456                   case '\n':
10457                   case '\r':
10458                     break;
10459                   default:
10460                     initial_position[i][j++] = CharToPiece(*p);
10461                     break;
10462                 }
10463             while (*p == ' ' || *p == '\t' ||
10464                    *p == '\n' || *p == '\r') p++;
10465
10466             if (strncmp(p, "black", strlen("black"))==0)
10467               blackPlaysFirst = TRUE;
10468             else
10469               blackPlaysFirst = FALSE;
10470             startedFromSetupPosition = TRUE;
10471
10472             CopyBoard(boards[0], initial_position);
10473             if (blackPlaysFirst) {
10474                 currentMove = forwardMostMove = backwardMostMove = 1;
10475                 CopyBoard(boards[1], initial_position);
10476                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10477                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10478                 timeRemaining[0][1] = whiteTimeRemaining;
10479                 timeRemaining[1][1] = blackTimeRemaining;
10480                 if (commentList[0] != NULL) {
10481                     commentList[1] = commentList[0];
10482                     commentList[0] = NULL;
10483                 }
10484             } else {
10485                 currentMove = forwardMostMove = backwardMostMove = 0;
10486             }
10487         }
10488         yyboardindex = forwardMostMove;
10489         cm = (ChessMove) Myylex();
10490     }
10491
10492     if (first.pr == NoProc) {
10493         StartChessProgram(&first);
10494     }
10495     InitChessProgram(&first, FALSE);
10496     SendToProgram("force\n", &first);
10497     if (startedFromSetupPosition) {
10498         SendBoard(&first, forwardMostMove);
10499     if (appData.debugMode) {
10500         fprintf(debugFP, "Load Game\n");
10501     }
10502         DisplayBothClocks();
10503     }
10504
10505     /* [HGM] server: flag to write setup moves in broadcast file as one */
10506     loadFlag = appData.suppressLoadMoves;
10507
10508     while (cm == Comment) {
10509         char *p;
10510         if (appData.debugMode)
10511           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10512         p = yy_text;
10513         AppendComment(currentMove, p, FALSE);
10514         yyboardindex = forwardMostMove;
10515         cm = (ChessMove) Myylex();
10516     }
10517
10518     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10519         cm == WhiteWins || cm == BlackWins ||
10520         cm == GameIsDrawn || cm == GameUnfinished) {
10521         DisplayMessage("", _("No moves in game"));
10522         if (cmailMsgLoaded) {
10523             if (appData.debugMode)
10524               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10525             ClearHighlights();
10526             flipView = FALSE;
10527         }
10528         DrawPosition(FALSE, boards[currentMove]);
10529         DisplayBothClocks();
10530         gameMode = EditGame;
10531         ModeHighlight();
10532         gameFileFP = NULL;
10533         cmailOldMove = 0;
10534         return TRUE;
10535     }
10536
10537     // [HGM] PV info: routine tests if comment empty
10538     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10539         DisplayComment(currentMove - 1, commentList[currentMove]);
10540     }
10541     if (!matchMode && appData.timeDelay != 0)
10542       DrawPosition(FALSE, boards[currentMove]);
10543
10544     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10545       programStats.ok_to_send = 1;
10546     }
10547
10548     /* if the first token after the PGN tags is a move
10549      * and not move number 1, retrieve it from the parser
10550      */
10551     if (cm != MoveNumberOne)
10552         LoadGameOneMove(cm);
10553
10554     /* load the remaining moves from the file */
10555     while (LoadGameOneMove(EndOfFile)) {
10556       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10557       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10558     }
10559
10560     /* rewind to the start of the game */
10561     currentMove = backwardMostMove;
10562
10563     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10564
10565     if (oldGameMode == AnalyzeFile ||
10566         oldGameMode == AnalyzeMode) {
10567       AnalyzeFileEvent();
10568     }
10569
10570     if (matchMode || appData.timeDelay == 0) {
10571       ToEndEvent();
10572       gameMode = EditGame;
10573       ModeHighlight();
10574     } else if (appData.timeDelay > 0) {
10575       AutoPlayGameLoop();
10576     }
10577
10578     if (appData.debugMode)
10579         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10580
10581     loadFlag = 0; /* [HGM] true game starts */
10582     return TRUE;
10583 }
10584
10585 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10586 int
10587 ReloadPosition(offset)
10588      int offset;
10589 {
10590     int positionNumber = lastLoadPositionNumber + offset;
10591     if (lastLoadPositionFP == NULL) {
10592         DisplayError(_("No position has been loaded yet"), 0);
10593         return FALSE;
10594     }
10595     if (positionNumber <= 0) {
10596         DisplayError(_("Can't back up any further"), 0);
10597         return FALSE;
10598     }
10599     return LoadPosition(lastLoadPositionFP, positionNumber,
10600                         lastLoadPositionTitle);
10601 }
10602
10603 /* Load the nth position from the given file */
10604 int
10605 LoadPositionFromFile(filename, n, title)
10606      char *filename;
10607      int n;
10608      char *title;
10609 {
10610     FILE *f;
10611     char buf[MSG_SIZ];
10612
10613     if (strcmp(filename, "-") == 0) {
10614         return LoadPosition(stdin, n, "stdin");
10615     } else {
10616         f = fopen(filename, "rb");
10617         if (f == NULL) {
10618             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10619             DisplayError(buf, errno);
10620             return FALSE;
10621         } else {
10622             return LoadPosition(f, n, title);
10623         }
10624     }
10625 }
10626
10627 /* Load the nth position from the given open file, and close it */
10628 int
10629 LoadPosition(f, positionNumber, title)
10630      FILE *f;
10631      int positionNumber;
10632      char *title;
10633 {
10634     char *p, line[MSG_SIZ];
10635     Board initial_position;
10636     int i, j, fenMode, pn;
10637
10638     if (gameMode == Training )
10639         SetTrainingModeOff();
10640
10641     if (gameMode != BeginningOfGame) {
10642         Reset(FALSE, TRUE);
10643     }
10644     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10645         fclose(lastLoadPositionFP);
10646     }
10647     if (positionNumber == 0) positionNumber = 1;
10648     lastLoadPositionFP = f;
10649     lastLoadPositionNumber = positionNumber;
10650     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10651     if (first.pr == NoProc) {
10652       StartChessProgram(&first);
10653       InitChessProgram(&first, FALSE);
10654     }
10655     pn = positionNumber;
10656     if (positionNumber < 0) {
10657         /* Negative position number means to seek to that byte offset */
10658         if (fseek(f, -positionNumber, 0) == -1) {
10659             DisplayError(_("Can't seek on position file"), 0);
10660             return FALSE;
10661         };
10662         pn = 1;
10663     } else {
10664         if (fseek(f, 0, 0) == -1) {
10665             if (f == lastLoadPositionFP ?
10666                 positionNumber == lastLoadPositionNumber + 1 :
10667                 positionNumber == 1) {
10668                 pn = 1;
10669             } else {
10670                 DisplayError(_("Can't seek on position file"), 0);
10671                 return FALSE;
10672             }
10673         }
10674     }
10675     /* See if this file is FEN or old-style xboard */
10676     if (fgets(line, MSG_SIZ, f) == NULL) {
10677         DisplayError(_("Position not found in file"), 0);
10678         return FALSE;
10679     }
10680     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10681     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10682
10683     if (pn >= 2) {
10684         if (fenMode || line[0] == '#') pn--;
10685         while (pn > 0) {
10686             /* skip positions before number pn */
10687             if (fgets(line, MSG_SIZ, f) == NULL) {
10688                 Reset(TRUE, TRUE);
10689                 DisplayError(_("Position not found in file"), 0);
10690                 return FALSE;
10691             }
10692             if (fenMode || line[0] == '#') pn--;
10693         }
10694     }
10695
10696     if (fenMode) {
10697         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10698             DisplayError(_("Bad FEN position in file"), 0);
10699             return FALSE;
10700         }
10701     } else {
10702         (void) fgets(line, MSG_SIZ, f);
10703         (void) fgets(line, MSG_SIZ, f);
10704
10705         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10706             (void) fgets(line, MSG_SIZ, f);
10707             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10708                 if (*p == ' ')
10709                   continue;
10710                 initial_position[i][j++] = CharToPiece(*p);
10711             }
10712         }
10713
10714         blackPlaysFirst = FALSE;
10715         if (!feof(f)) {
10716             (void) fgets(line, MSG_SIZ, f);
10717             if (strncmp(line, "black", strlen("black"))==0)
10718               blackPlaysFirst = TRUE;
10719         }
10720     }
10721     startedFromSetupPosition = TRUE;
10722
10723     SendToProgram("force\n", &first);
10724     CopyBoard(boards[0], initial_position);
10725     if (blackPlaysFirst) {
10726         currentMove = forwardMostMove = backwardMostMove = 1;
10727         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10728         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10729         CopyBoard(boards[1], initial_position);
10730         DisplayMessage("", _("Black to play"));
10731     } else {
10732         currentMove = forwardMostMove = backwardMostMove = 0;
10733         DisplayMessage("", _("White to play"));
10734     }
10735     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10736     SendBoard(&first, forwardMostMove);
10737     if (appData.debugMode) {
10738 int i, j;
10739   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10740   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10741         fprintf(debugFP, "Load Position\n");
10742     }
10743
10744     if (positionNumber > 1) {
10745       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10746         DisplayTitle(line);
10747     } else {
10748         DisplayTitle(title);
10749     }
10750     gameMode = EditGame;
10751     ModeHighlight();
10752     ResetClocks();
10753     timeRemaining[0][1] = whiteTimeRemaining;
10754     timeRemaining[1][1] = blackTimeRemaining;
10755     DrawPosition(FALSE, boards[currentMove]);
10756
10757     return TRUE;
10758 }
10759
10760
10761 void
10762 CopyPlayerNameIntoFileName(dest, src)
10763      char **dest, *src;
10764 {
10765     while (*src != NULLCHAR && *src != ',') {
10766         if (*src == ' ') {
10767             *(*dest)++ = '_';
10768             src++;
10769         } else {
10770             *(*dest)++ = *src++;
10771         }
10772     }
10773 }
10774
10775 char *DefaultFileName(ext)
10776      char *ext;
10777 {
10778     static char def[MSG_SIZ];
10779     char *p;
10780
10781     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10782         p = def;
10783         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10784         *p++ = '-';
10785         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10786         *p++ = '.';
10787         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10788     } else {
10789         def[0] = NULLCHAR;
10790     }
10791     return def;
10792 }
10793
10794 /* Save the current game to the given file */
10795 int
10796 SaveGameToFile(filename, append)
10797      char *filename;
10798      int append;
10799 {
10800     FILE *f;
10801     char buf[MSG_SIZ];
10802
10803     if (strcmp(filename, "-") == 0) {
10804         return SaveGame(stdout, 0, NULL);
10805     } else {
10806         f = fopen(filename, append ? "a" : "w");
10807         if (f == NULL) {
10808             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10809             DisplayError(buf, errno);
10810             return FALSE;
10811         } else {
10812             return SaveGame(f, 0, NULL);
10813         }
10814     }
10815 }
10816
10817 char *
10818 SavePart(str)
10819      char *str;
10820 {
10821     static char buf[MSG_SIZ];
10822     char *p;
10823
10824     p = strchr(str, ' ');
10825     if (p == NULL) return str;
10826     strncpy(buf, str, p - str);
10827     buf[p - str] = NULLCHAR;
10828     return buf;
10829 }
10830
10831 #define PGN_MAX_LINE 75
10832
10833 #define PGN_SIDE_WHITE  0
10834 #define PGN_SIDE_BLACK  1
10835
10836 /* [AS] */
10837 static int FindFirstMoveOutOfBook( int side )
10838 {
10839     int result = -1;
10840
10841     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10842         int index = backwardMostMove;
10843         int has_book_hit = 0;
10844
10845         if( (index % 2) != side ) {
10846             index++;
10847         }
10848
10849         while( index < forwardMostMove ) {
10850             /* Check to see if engine is in book */
10851             int depth = pvInfoList[index].depth;
10852             int score = pvInfoList[index].score;
10853             int in_book = 0;
10854
10855             if( depth <= 2 ) {
10856                 in_book = 1;
10857             }
10858             else if( score == 0 && depth == 63 ) {
10859                 in_book = 1; /* Zappa */
10860             }
10861             else if( score == 2 && depth == 99 ) {
10862                 in_book = 1; /* Abrok */
10863             }
10864
10865             has_book_hit += in_book;
10866
10867             if( ! in_book ) {
10868                 result = index;
10869
10870                 break;
10871             }
10872
10873             index += 2;
10874         }
10875     }
10876
10877     return result;
10878 }
10879
10880 /* [AS] */
10881 void GetOutOfBookInfo( char * buf )
10882 {
10883     int oob[2];
10884     int i;
10885     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10886
10887     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10888     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10889
10890     *buf = '\0';
10891
10892     if( oob[0] >= 0 || oob[1] >= 0 ) {
10893         for( i=0; i<2; i++ ) {
10894             int idx = oob[i];
10895
10896             if( idx >= 0 ) {
10897                 if( i > 0 && oob[0] >= 0 ) {
10898                     strcat( buf, "   " );
10899                 }
10900
10901                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10902                 sprintf( buf+strlen(buf), "%s%.2f",
10903                     pvInfoList[idx].score >= 0 ? "+" : "",
10904                     pvInfoList[idx].score / 100.0 );
10905             }
10906         }
10907     }
10908 }
10909
10910 /* Save game in PGN style and close the file */
10911 int
10912 SaveGamePGN(f)
10913      FILE *f;
10914 {
10915     int i, offset, linelen, newblock;
10916     time_t tm;
10917 //    char *movetext;
10918     char numtext[32];
10919     int movelen, numlen, blank;
10920     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10921
10922     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10923
10924     tm = time((time_t *) NULL);
10925
10926     PrintPGNTags(f, &gameInfo);
10927
10928     if (backwardMostMove > 0 || startedFromSetupPosition) {
10929         char *fen = PositionToFEN(backwardMostMove, NULL);
10930         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10931         fprintf(f, "\n{--------------\n");
10932         PrintPosition(f, backwardMostMove);
10933         fprintf(f, "--------------}\n");
10934         free(fen);
10935     }
10936     else {
10937         /* [AS] Out of book annotation */
10938         if( appData.saveOutOfBookInfo ) {
10939             char buf[64];
10940
10941             GetOutOfBookInfo( buf );
10942
10943             if( buf[0] != '\0' ) {
10944                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10945             }
10946         }
10947
10948         fprintf(f, "\n");
10949     }
10950
10951     i = backwardMostMove;
10952     linelen = 0;
10953     newblock = TRUE;
10954
10955     while (i < forwardMostMove) {
10956         /* Print comments preceding this move */
10957         if (commentList[i] != NULL) {
10958             if (linelen > 0) fprintf(f, "\n");
10959             fprintf(f, "%s", commentList[i]);
10960             linelen = 0;
10961             newblock = TRUE;
10962         }
10963
10964         /* Format move number */
10965         if ((i % 2) == 0)
10966           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10967         else
10968           if (newblock)
10969             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10970           else
10971             numtext[0] = NULLCHAR;
10972
10973         numlen = strlen(numtext);
10974         newblock = FALSE;
10975
10976         /* Print move number */
10977         blank = linelen > 0 && numlen > 0;
10978         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10979             fprintf(f, "\n");
10980             linelen = 0;
10981             blank = 0;
10982         }
10983         if (blank) {
10984             fprintf(f, " ");
10985             linelen++;
10986         }
10987         fprintf(f, "%s", numtext);
10988         linelen += numlen;
10989
10990         /* Get move */
10991         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10992         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10993
10994         /* Print move */
10995         blank = linelen > 0 && movelen > 0;
10996         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10997             fprintf(f, "\n");
10998             linelen = 0;
10999             blank = 0;
11000         }
11001         if (blank) {
11002             fprintf(f, " ");
11003             linelen++;
11004         }
11005         fprintf(f, "%s", move_buffer);
11006         linelen += movelen;
11007
11008         /* [AS] Add PV info if present */
11009         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11010             /* [HGM] add time */
11011             char buf[MSG_SIZ]; int seconds;
11012
11013             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11014
11015             if( seconds <= 0)
11016               buf[0] = 0;
11017             else
11018               if( seconds < 30 )
11019                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11020               else
11021                 {
11022                   seconds = (seconds + 4)/10; // round to full seconds
11023                   if( seconds < 60 )
11024                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11025                   else
11026                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11027                 }
11028
11029             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11030                       pvInfoList[i].score >= 0 ? "+" : "",
11031                       pvInfoList[i].score / 100.0,
11032                       pvInfoList[i].depth,
11033                       buf );
11034
11035             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11036
11037             /* Print score/depth */
11038             blank = linelen > 0 && movelen > 0;
11039             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11040                 fprintf(f, "\n");
11041                 linelen = 0;
11042                 blank = 0;
11043             }
11044             if (blank) {
11045                 fprintf(f, " ");
11046                 linelen++;
11047             }
11048             fprintf(f, "%s", move_buffer);
11049             linelen += movelen;
11050         }
11051
11052         i++;
11053     }
11054
11055     /* Start a new line */
11056     if (linelen > 0) fprintf(f, "\n");
11057
11058     /* Print comments after last move */
11059     if (commentList[i] != NULL) {
11060         fprintf(f, "%s\n", commentList[i]);
11061     }
11062
11063     /* Print result */
11064     if (gameInfo.resultDetails != NULL &&
11065         gameInfo.resultDetails[0] != NULLCHAR) {
11066         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11067                 PGNResult(gameInfo.result));
11068     } else {
11069         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11070     }
11071
11072     fclose(f);
11073     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11074     return TRUE;
11075 }
11076
11077 /* Save game in old style and close the file */
11078 int
11079 SaveGameOldStyle(f)
11080      FILE *f;
11081 {
11082     int i, offset;
11083     time_t tm;
11084
11085     tm = time((time_t *) NULL);
11086
11087     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11088     PrintOpponents(f);
11089
11090     if (backwardMostMove > 0 || startedFromSetupPosition) {
11091         fprintf(f, "\n[--------------\n");
11092         PrintPosition(f, backwardMostMove);
11093         fprintf(f, "--------------]\n");
11094     } else {
11095         fprintf(f, "\n");
11096     }
11097
11098     i = backwardMostMove;
11099     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11100
11101     while (i < forwardMostMove) {
11102         if (commentList[i] != NULL) {
11103             fprintf(f, "[%s]\n", commentList[i]);
11104         }
11105
11106         if ((i % 2) == 1) {
11107             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11108             i++;
11109         } else {
11110             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11111             i++;
11112             if (commentList[i] != NULL) {
11113                 fprintf(f, "\n");
11114                 continue;
11115             }
11116             if (i >= forwardMostMove) {
11117                 fprintf(f, "\n");
11118                 break;
11119             }
11120             fprintf(f, "%s\n", parseList[i]);
11121             i++;
11122         }
11123     }
11124
11125     if (commentList[i] != NULL) {
11126         fprintf(f, "[%s]\n", commentList[i]);
11127     }
11128
11129     /* This isn't really the old style, but it's close enough */
11130     if (gameInfo.resultDetails != NULL &&
11131         gameInfo.resultDetails[0] != NULLCHAR) {
11132         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11133                 gameInfo.resultDetails);
11134     } else {
11135         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11136     }
11137
11138     fclose(f);
11139     return TRUE;
11140 }
11141
11142 /* Save the current game to open file f and close the file */
11143 int
11144 SaveGame(f, dummy, dummy2)
11145      FILE *f;
11146      int dummy;
11147      char *dummy2;
11148 {
11149     if (gameMode == EditPosition) EditPositionDone(TRUE);
11150     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11151     if (appData.oldSaveStyle)
11152       return SaveGameOldStyle(f);
11153     else
11154       return SaveGamePGN(f);
11155 }
11156
11157 /* Save the current position to the given file */
11158 int
11159 SavePositionToFile(filename)
11160      char *filename;
11161 {
11162     FILE *f;
11163     char buf[MSG_SIZ];
11164
11165     if (strcmp(filename, "-") == 0) {
11166         return SavePosition(stdout, 0, NULL);
11167     } else {
11168         f = fopen(filename, "a");
11169         if (f == NULL) {
11170             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11171             DisplayError(buf, errno);
11172             return FALSE;
11173         } else {
11174             SavePosition(f, 0, NULL);
11175             return TRUE;
11176         }
11177     }
11178 }
11179
11180 /* Save the current position to the given open file and close the file */
11181 int
11182 SavePosition(f, dummy, dummy2)
11183      FILE *f;
11184      int dummy;
11185      char *dummy2;
11186 {
11187     time_t tm;
11188     char *fen;
11189
11190     if (gameMode == EditPosition) EditPositionDone(TRUE);
11191     if (appData.oldSaveStyle) {
11192         tm = time((time_t *) NULL);
11193
11194         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11195         PrintOpponents(f);
11196         fprintf(f, "[--------------\n");
11197         PrintPosition(f, currentMove);
11198         fprintf(f, "--------------]\n");
11199     } else {
11200         fen = PositionToFEN(currentMove, NULL);
11201         fprintf(f, "%s\n", fen);
11202         free(fen);
11203     }
11204     fclose(f);
11205     return TRUE;
11206 }
11207
11208 void
11209 ReloadCmailMsgEvent(unregister)
11210      int unregister;
11211 {
11212 #if !WIN32
11213     static char *inFilename = NULL;
11214     static char *outFilename;
11215     int i;
11216     struct stat inbuf, outbuf;
11217     int status;
11218
11219     /* Any registered moves are unregistered if unregister is set, */
11220     /* i.e. invoked by the signal handler */
11221     if (unregister) {
11222         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11223             cmailMoveRegistered[i] = FALSE;
11224             if (cmailCommentList[i] != NULL) {
11225                 free(cmailCommentList[i]);
11226                 cmailCommentList[i] = NULL;
11227             }
11228         }
11229         nCmailMovesRegistered = 0;
11230     }
11231
11232     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11233         cmailResult[i] = CMAIL_NOT_RESULT;
11234     }
11235     nCmailResults = 0;
11236
11237     if (inFilename == NULL) {
11238         /* Because the filenames are static they only get malloced once  */
11239         /* and they never get freed                                      */
11240         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11241         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11242
11243         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11244         sprintf(outFilename, "%s.out", appData.cmailGameName);
11245     }
11246
11247     status = stat(outFilename, &outbuf);
11248     if (status < 0) {
11249         cmailMailedMove = FALSE;
11250     } else {
11251         status = stat(inFilename, &inbuf);
11252         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11253     }
11254
11255     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11256        counts the games, notes how each one terminated, etc.
11257
11258        It would be nice to remove this kludge and instead gather all
11259        the information while building the game list.  (And to keep it
11260        in the game list nodes instead of having a bunch of fixed-size
11261        parallel arrays.)  Note this will require getting each game's
11262        termination from the PGN tags, as the game list builder does
11263        not process the game moves.  --mann
11264        */
11265     cmailMsgLoaded = TRUE;
11266     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11267
11268     /* Load first game in the file or popup game menu */
11269     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11270
11271 #endif /* !WIN32 */
11272     return;
11273 }
11274
11275 int
11276 RegisterMove()
11277 {
11278     FILE *f;
11279     char string[MSG_SIZ];
11280
11281     if (   cmailMailedMove
11282         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11283         return TRUE;            /* Allow free viewing  */
11284     }
11285
11286     /* Unregister move to ensure that we don't leave RegisterMove        */
11287     /* with the move registered when the conditions for registering no   */
11288     /* longer hold                                                       */
11289     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11290         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11291         nCmailMovesRegistered --;
11292
11293         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11294           {
11295               free(cmailCommentList[lastLoadGameNumber - 1]);
11296               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11297           }
11298     }
11299
11300     if (cmailOldMove == -1) {
11301         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11302         return FALSE;
11303     }
11304
11305     if (currentMove > cmailOldMove + 1) {
11306         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11307         return FALSE;
11308     }
11309
11310     if (currentMove < cmailOldMove) {
11311         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11312         return FALSE;
11313     }
11314
11315     if (forwardMostMove > currentMove) {
11316         /* Silently truncate extra moves */
11317         TruncateGame();
11318     }
11319
11320     if (   (currentMove == cmailOldMove + 1)
11321         || (   (currentMove == cmailOldMove)
11322             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11323                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11324         if (gameInfo.result != GameUnfinished) {
11325             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11326         }
11327
11328         if (commentList[currentMove] != NULL) {
11329             cmailCommentList[lastLoadGameNumber - 1]
11330               = StrSave(commentList[currentMove]);
11331         }
11332         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11333
11334         if (appData.debugMode)
11335           fprintf(debugFP, "Saving %s for game %d\n",
11336                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11337
11338         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11339
11340         f = fopen(string, "w");
11341         if (appData.oldSaveStyle) {
11342             SaveGameOldStyle(f); /* also closes the file */
11343
11344             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11345             f = fopen(string, "w");
11346             SavePosition(f, 0, NULL); /* also closes the file */
11347         } else {
11348             fprintf(f, "{--------------\n");
11349             PrintPosition(f, currentMove);
11350             fprintf(f, "--------------}\n\n");
11351
11352             SaveGame(f, 0, NULL); /* also closes the file*/
11353         }
11354
11355         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11356         nCmailMovesRegistered ++;
11357     } else if (nCmailGames == 1) {
11358         DisplayError(_("You have not made a move yet"), 0);
11359         return FALSE;
11360     }
11361
11362     return TRUE;
11363 }
11364
11365 void
11366 MailMoveEvent()
11367 {
11368 #if !WIN32
11369     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11370     FILE *commandOutput;
11371     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11372     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11373     int nBuffers;
11374     int i;
11375     int archived;
11376     char *arcDir;
11377
11378     if (! cmailMsgLoaded) {
11379         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11380         return;
11381     }
11382
11383     if (nCmailGames == nCmailResults) {
11384         DisplayError(_("No unfinished games"), 0);
11385         return;
11386     }
11387
11388 #if CMAIL_PROHIBIT_REMAIL
11389     if (cmailMailedMove) {
11390       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);
11391         DisplayError(msg, 0);
11392         return;
11393     }
11394 #endif
11395
11396     if (! (cmailMailedMove || RegisterMove())) return;
11397
11398     if (   cmailMailedMove
11399         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11400       snprintf(string, MSG_SIZ, partCommandString,
11401                appData.debugMode ? " -v" : "", appData.cmailGameName);
11402         commandOutput = popen(string, "r");
11403
11404         if (commandOutput == NULL) {
11405             DisplayError(_("Failed to invoke cmail"), 0);
11406         } else {
11407             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11408                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11409             }
11410             if (nBuffers > 1) {
11411                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11412                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11413                 nBytes = MSG_SIZ - 1;
11414             } else {
11415                 (void) memcpy(msg, buffer, nBytes);
11416             }
11417             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11418
11419             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11420                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11421
11422                 archived = TRUE;
11423                 for (i = 0; i < nCmailGames; i ++) {
11424                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11425                         archived = FALSE;
11426                     }
11427                 }
11428                 if (   archived
11429                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11430                         != NULL)) {
11431                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11432                            arcDir,
11433                            appData.cmailGameName,
11434                            gameInfo.date);
11435                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11436                     cmailMsgLoaded = FALSE;
11437                 }
11438             }
11439
11440             DisplayInformation(msg);
11441             pclose(commandOutput);
11442         }
11443     } else {
11444         if ((*cmailMsg) != '\0') {
11445             DisplayInformation(cmailMsg);
11446         }
11447     }
11448
11449     return;
11450 #endif /* !WIN32 */
11451 }
11452
11453 char *
11454 CmailMsg()
11455 {
11456 #if WIN32
11457     return NULL;
11458 #else
11459     int  prependComma = 0;
11460     char number[5];
11461     char string[MSG_SIZ];       /* Space for game-list */
11462     int  i;
11463
11464     if (!cmailMsgLoaded) return "";
11465
11466     if (cmailMailedMove) {
11467       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11468     } else {
11469         /* Create a list of games left */
11470       snprintf(string, MSG_SIZ, "[");
11471         for (i = 0; i < nCmailGames; i ++) {
11472             if (! (   cmailMoveRegistered[i]
11473                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11474                 if (prependComma) {
11475                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11476                 } else {
11477                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11478                     prependComma = 1;
11479                 }
11480
11481                 strcat(string, number);
11482             }
11483         }
11484         strcat(string, "]");
11485
11486         if (nCmailMovesRegistered + nCmailResults == 0) {
11487             switch (nCmailGames) {
11488               case 1:
11489                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11490                 break;
11491
11492               case 2:
11493                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11494                 break;
11495
11496               default:
11497                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11498                          nCmailGames);
11499                 break;
11500             }
11501         } else {
11502             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11503               case 1:
11504                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11505                          string);
11506                 break;
11507
11508               case 0:
11509                 if (nCmailResults == nCmailGames) {
11510                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11511                 } else {
11512                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11513                 }
11514                 break;
11515
11516               default:
11517                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11518                          string);
11519             }
11520         }
11521     }
11522     return cmailMsg;
11523 #endif /* WIN32 */
11524 }
11525
11526 void
11527 ResetGameEvent()
11528 {
11529     if (gameMode == Training)
11530       SetTrainingModeOff();
11531
11532     Reset(TRUE, TRUE);
11533     cmailMsgLoaded = FALSE;
11534     if (appData.icsActive) {
11535       SendToICS(ics_prefix);
11536       SendToICS("refresh\n");
11537     }
11538 }
11539
11540 void
11541 ExitEvent(status)
11542      int status;
11543 {
11544     exiting++;
11545     if (exiting > 2) {
11546       /* Give up on clean exit */
11547       exit(status);
11548     }
11549     if (exiting > 1) {
11550       /* Keep trying for clean exit */
11551       return;
11552     }
11553
11554     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11555
11556     if (telnetISR != NULL) {
11557       RemoveInputSource(telnetISR);
11558     }
11559     if (icsPR != NoProc) {
11560       DestroyChildProcess(icsPR, TRUE);
11561     }
11562
11563     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11564     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11565
11566     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11567     /* make sure this other one finishes before killing it!                  */
11568     if(endingGame) { int count = 0;
11569         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11570         while(endingGame && count++ < 10) DoSleep(1);
11571         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11572     }
11573
11574     /* Kill off chess programs */
11575     if (first.pr != NoProc) {
11576         ExitAnalyzeMode();
11577
11578         DoSleep( appData.delayBeforeQuit );
11579         SendToProgram("quit\n", &first);
11580         DoSleep( appData.delayAfterQuit );
11581         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11582     }
11583     if (second.pr != NoProc) {
11584         DoSleep( appData.delayBeforeQuit );
11585         SendToProgram("quit\n", &second);
11586         DoSleep( appData.delayAfterQuit );
11587         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11588     }
11589     if (first.isr != NULL) {
11590         RemoveInputSource(first.isr);
11591     }
11592     if (second.isr != NULL) {
11593         RemoveInputSource(second.isr);
11594     }
11595
11596     ShutDownFrontEnd();
11597     exit(status);
11598 }
11599
11600 void
11601 PauseEvent()
11602 {
11603     if (appData.debugMode)
11604         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11605     if (pausing) {
11606         pausing = FALSE;
11607         ModeHighlight();
11608         if (gameMode == MachinePlaysWhite ||
11609             gameMode == MachinePlaysBlack) {
11610             StartClocks();
11611         } else {
11612             DisplayBothClocks();
11613         }
11614         if (gameMode == PlayFromGameFile) {
11615             if (appData.timeDelay >= 0)
11616                 AutoPlayGameLoop();
11617         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11618             Reset(FALSE, TRUE);
11619             SendToICS(ics_prefix);
11620             SendToICS("refresh\n");
11621         } else if (currentMove < forwardMostMove) {
11622             ForwardInner(forwardMostMove);
11623         }
11624         pauseExamInvalid = FALSE;
11625     } else {
11626         switch (gameMode) {
11627           default:
11628             return;
11629           case IcsExamining:
11630             pauseExamForwardMostMove = forwardMostMove;
11631             pauseExamInvalid = FALSE;
11632             /* fall through */
11633           case IcsObserving:
11634           case IcsPlayingWhite:
11635           case IcsPlayingBlack:
11636             pausing = TRUE;
11637             ModeHighlight();
11638             return;
11639           case PlayFromGameFile:
11640             (void) StopLoadGameTimer();
11641             pausing = TRUE;
11642             ModeHighlight();
11643             break;
11644           case BeginningOfGame:
11645             if (appData.icsActive) return;
11646             /* else fall through */
11647           case MachinePlaysWhite:
11648           case MachinePlaysBlack:
11649           case TwoMachinesPlay:
11650             if (forwardMostMove == 0)
11651               return;           /* don't pause if no one has moved */
11652             if ((gameMode == MachinePlaysWhite &&
11653                  !WhiteOnMove(forwardMostMove)) ||
11654                 (gameMode == MachinePlaysBlack &&
11655                  WhiteOnMove(forwardMostMove))) {
11656                 StopClocks();
11657             }
11658             pausing = TRUE;
11659             ModeHighlight();
11660             break;
11661         }
11662     }
11663 }
11664
11665 void
11666 EditCommentEvent()
11667 {
11668     char title[MSG_SIZ];
11669
11670     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11671       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11672     } else {
11673       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11674                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11675                parseList[currentMove - 1]);
11676     }
11677
11678     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11679 }
11680
11681
11682 void
11683 EditTagsEvent()
11684 {
11685     char *tags = PGNTags(&gameInfo);
11686     EditTagsPopUp(tags, NULL);
11687     free(tags);
11688 }
11689
11690 void
11691 AnalyzeModeEvent()
11692 {
11693     if (appData.noChessProgram || gameMode == AnalyzeMode)
11694       return;
11695
11696     if (gameMode != AnalyzeFile) {
11697         if (!appData.icsEngineAnalyze) {
11698                EditGameEvent();
11699                if (gameMode != EditGame) return;
11700         }
11701         ResurrectChessProgram();
11702         SendToProgram("analyze\n", &first);
11703         first.analyzing = TRUE;
11704         /*first.maybeThinking = TRUE;*/
11705         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11706         EngineOutputPopUp();
11707     }
11708     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11709     pausing = FALSE;
11710     ModeHighlight();
11711     SetGameInfo();
11712
11713     StartAnalysisClock();
11714     GetTimeMark(&lastNodeCountTime);
11715     lastNodeCount = 0;
11716 }
11717
11718 void
11719 AnalyzeFileEvent()
11720 {
11721     if (appData.noChessProgram || gameMode == AnalyzeFile)
11722       return;
11723
11724     if (gameMode != AnalyzeMode) {
11725         EditGameEvent();
11726         if (gameMode != EditGame) return;
11727         ResurrectChessProgram();
11728         SendToProgram("analyze\n", &first);
11729         first.analyzing = TRUE;
11730         /*first.maybeThinking = TRUE;*/
11731         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11732         EngineOutputPopUp();
11733     }
11734     gameMode = AnalyzeFile;
11735     pausing = FALSE;
11736     ModeHighlight();
11737     SetGameInfo();
11738
11739     StartAnalysisClock();
11740     GetTimeMark(&lastNodeCountTime);
11741     lastNodeCount = 0;
11742 }
11743
11744 void
11745 MachineWhiteEvent()
11746 {
11747     char buf[MSG_SIZ];
11748     char *bookHit = NULL;
11749
11750     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11751       return;
11752
11753
11754     if (gameMode == PlayFromGameFile ||
11755         gameMode == TwoMachinesPlay  ||
11756         gameMode == Training         ||
11757         gameMode == AnalyzeMode      ||
11758         gameMode == EndOfGame)
11759         EditGameEvent();
11760
11761     if (gameMode == EditPosition)
11762         EditPositionDone(TRUE);
11763
11764     if (!WhiteOnMove(currentMove)) {
11765         DisplayError(_("It is not White's turn"), 0);
11766         return;
11767     }
11768
11769     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11770       ExitAnalyzeMode();
11771
11772     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11773         gameMode == AnalyzeFile)
11774         TruncateGame();
11775
11776     ResurrectChessProgram();    /* in case it isn't running */
11777     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11778         gameMode = MachinePlaysWhite;
11779         ResetClocks();
11780     } else
11781     gameMode = MachinePlaysWhite;
11782     pausing = FALSE;
11783     ModeHighlight();
11784     SetGameInfo();
11785     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11786     DisplayTitle(buf);
11787     if (first.sendName) {
11788       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11789       SendToProgram(buf, &first);
11790     }
11791     if (first.sendTime) {
11792       if (first.useColors) {
11793         SendToProgram("black\n", &first); /*gnu kludge*/
11794       }
11795       SendTimeRemaining(&first, TRUE);
11796     }
11797     if (first.useColors) {
11798       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11799     }
11800     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11801     SetMachineThinkingEnables();
11802     first.maybeThinking = TRUE;
11803     StartClocks();
11804     firstMove = FALSE;
11805
11806     if (appData.autoFlipView && !flipView) {
11807       flipView = !flipView;
11808       DrawPosition(FALSE, NULL);
11809       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11810     }
11811
11812     if(bookHit) { // [HGM] book: simulate book reply
11813         static char bookMove[MSG_SIZ]; // a bit generous?
11814
11815         programStats.nodes = programStats.depth = programStats.time =
11816         programStats.score = programStats.got_only_move = 0;
11817         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11818
11819         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11820         strcat(bookMove, bookHit);
11821         HandleMachineMove(bookMove, &first);
11822     }
11823 }
11824
11825 void
11826 MachineBlackEvent()
11827 {
11828   char buf[MSG_SIZ];
11829   char *bookHit = NULL;
11830
11831     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11832         return;
11833
11834
11835     if (gameMode == PlayFromGameFile ||
11836         gameMode == TwoMachinesPlay  ||
11837         gameMode == Training         ||
11838         gameMode == AnalyzeMode      ||
11839         gameMode == EndOfGame)
11840         EditGameEvent();
11841
11842     if (gameMode == EditPosition)
11843         EditPositionDone(TRUE);
11844
11845     if (WhiteOnMove(currentMove)) {
11846         DisplayError(_("It is not Black's turn"), 0);
11847         return;
11848     }
11849
11850     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11851       ExitAnalyzeMode();
11852
11853     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11854         gameMode == AnalyzeFile)
11855         TruncateGame();
11856
11857     ResurrectChessProgram();    /* in case it isn't running */
11858     gameMode = MachinePlaysBlack;
11859     pausing = FALSE;
11860     ModeHighlight();
11861     SetGameInfo();
11862     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11863     DisplayTitle(buf);
11864     if (first.sendName) {
11865       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11866       SendToProgram(buf, &first);
11867     }
11868     if (first.sendTime) {
11869       if (first.useColors) {
11870         SendToProgram("white\n", &first); /*gnu kludge*/
11871       }
11872       SendTimeRemaining(&first, FALSE);
11873     }
11874     if (first.useColors) {
11875       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11876     }
11877     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11878     SetMachineThinkingEnables();
11879     first.maybeThinking = TRUE;
11880     StartClocks();
11881
11882     if (appData.autoFlipView && flipView) {
11883       flipView = !flipView;
11884       DrawPosition(FALSE, NULL);
11885       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11886     }
11887     if(bookHit) { // [HGM] book: simulate book reply
11888         static char bookMove[MSG_SIZ]; // a bit generous?
11889
11890         programStats.nodes = programStats.depth = programStats.time =
11891         programStats.score = programStats.got_only_move = 0;
11892         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11893
11894         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11895         strcat(bookMove, bookHit);
11896         HandleMachineMove(bookMove, &first);
11897     }
11898 }
11899
11900
11901 void
11902 DisplayTwoMachinesTitle()
11903 {
11904     char buf[MSG_SIZ];
11905     if (appData.matchGames > 0) {
11906         if (first.twoMachinesColor[0] == 'w') {
11907           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11908                    gameInfo.white, gameInfo.black,
11909                    first.matchWins, second.matchWins,
11910                    matchGame - 1 - (first.matchWins + second.matchWins));
11911         } else {
11912           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11913                    gameInfo.white, gameInfo.black,
11914                    second.matchWins, first.matchWins,
11915                    matchGame - 1 - (first.matchWins + second.matchWins));
11916         }
11917     } else {
11918       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11919     }
11920     DisplayTitle(buf);
11921 }
11922
11923 void
11924 SettingsMenuIfReady()
11925 {
11926   if (second.lastPing != second.lastPong) {
11927     DisplayMessage("", _("Waiting for second chess program"));
11928     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11929     return;
11930   }
11931   ThawUI();
11932   DisplayMessage("", "");
11933   SettingsPopUp(&second);
11934 }
11935
11936 int
11937 WaitForSecond(DelayedEventCallback retry)
11938 {
11939     if (second.pr == NULL) {
11940         StartChessProgram(&second);
11941         if (second.protocolVersion == 1) {
11942           retry();
11943         } else {
11944           /* kludge: allow timeout for initial "feature" command */
11945           FreezeUI();
11946           DisplayMessage("", _("Starting second chess program"));
11947           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11948         }
11949         return 1;
11950     }
11951     return 0;
11952 }
11953
11954 void
11955 TwoMachinesEvent P((void))
11956 {
11957     int i;
11958     char buf[MSG_SIZ];
11959     ChessProgramState *onmove;
11960     char *bookHit = NULL;
11961
11962     if (appData.noChessProgram) return;
11963
11964     switch (gameMode) {
11965       case TwoMachinesPlay:
11966         return;
11967       case MachinePlaysWhite:
11968       case MachinePlaysBlack:
11969         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11970             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11971             return;
11972         }
11973         /* fall through */
11974       case BeginningOfGame:
11975       case PlayFromGameFile:
11976       case EndOfGame:
11977         EditGameEvent();
11978         if (gameMode != EditGame) return;
11979         break;
11980       case EditPosition:
11981         EditPositionDone(TRUE);
11982         break;
11983       case AnalyzeMode:
11984       case AnalyzeFile:
11985         ExitAnalyzeMode();
11986         break;
11987       case EditGame:
11988       default:
11989         break;
11990     }
11991
11992 //    forwardMostMove = currentMove;
11993     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11994     ResurrectChessProgram();    /* in case first program isn't running */
11995
11996     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11997     DisplayMessage("", "");
11998     InitChessProgram(&second, FALSE);
11999     SendToProgram("force\n", &second);
12000     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12001       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12002       return;
12003     }
12004     if (startedFromSetupPosition) {
12005         SendBoard(&second, backwardMostMove);
12006     if (appData.debugMode) {
12007         fprintf(debugFP, "Two Machines\n");
12008     }
12009     }
12010     for (i = backwardMostMove; i < forwardMostMove; i++) {
12011         SendMoveToProgram(i, &second);
12012     }
12013
12014     gameMode = TwoMachinesPlay;
12015     pausing = FALSE;
12016     ModeHighlight();
12017     SetGameInfo();
12018     DisplayTwoMachinesTitle();
12019     firstMove = TRUE;
12020     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12021         onmove = &first;
12022     } else {
12023         onmove = &second;
12024     }
12025
12026     SendToProgram(first.computerString, &first);
12027     if (first.sendName) {
12028       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12029       SendToProgram(buf, &first);
12030     }
12031     SendToProgram(second.computerString, &second);
12032     if (second.sendName) {
12033       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12034       SendToProgram(buf, &second);
12035     }
12036
12037     ResetClocks();
12038     if (!first.sendTime || !second.sendTime) {
12039         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12040         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12041     }
12042     if (onmove->sendTime) {
12043       if (onmove->useColors) {
12044         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12045       }
12046       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12047     }
12048     if (onmove->useColors) {
12049       SendToProgram(onmove->twoMachinesColor, onmove);
12050     }
12051     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12052 //    SendToProgram("go\n", onmove);
12053     onmove->maybeThinking = TRUE;
12054     SetMachineThinkingEnables();
12055
12056     StartClocks();
12057
12058     if(bookHit) { // [HGM] book: simulate book reply
12059         static char bookMove[MSG_SIZ]; // a bit generous?
12060
12061         programStats.nodes = programStats.depth = programStats.time =
12062         programStats.score = programStats.got_only_move = 0;
12063         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12064
12065         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12066         strcat(bookMove, bookHit);
12067         savedMessage = bookMove; // args for deferred call
12068         savedState = onmove;
12069         ScheduleDelayedEvent(DeferredBookMove, 1);
12070     }
12071 }
12072
12073 void
12074 TrainingEvent()
12075 {
12076     if (gameMode == Training) {
12077       SetTrainingModeOff();
12078       gameMode = PlayFromGameFile;
12079       DisplayMessage("", _("Training mode off"));
12080     } else {
12081       gameMode = Training;
12082       animateTraining = appData.animate;
12083
12084       /* make sure we are not already at the end of the game */
12085       if (currentMove < forwardMostMove) {
12086         SetTrainingModeOn();
12087         DisplayMessage("", _("Training mode on"));
12088       } else {
12089         gameMode = PlayFromGameFile;
12090         DisplayError(_("Already at end of game"), 0);
12091       }
12092     }
12093     ModeHighlight();
12094 }
12095
12096 void
12097 IcsClientEvent()
12098 {
12099     if (!appData.icsActive) return;
12100     switch (gameMode) {
12101       case IcsPlayingWhite:
12102       case IcsPlayingBlack:
12103       case IcsObserving:
12104       case IcsIdle:
12105       case BeginningOfGame:
12106       case IcsExamining:
12107         return;
12108
12109       case EditGame:
12110         break;
12111
12112       case EditPosition:
12113         EditPositionDone(TRUE);
12114         break;
12115
12116       case AnalyzeMode:
12117       case AnalyzeFile:
12118         ExitAnalyzeMode();
12119         break;
12120
12121       default:
12122         EditGameEvent();
12123         break;
12124     }
12125
12126     gameMode = IcsIdle;
12127     ModeHighlight();
12128     return;
12129 }
12130
12131
12132 void
12133 EditGameEvent()
12134 {
12135     int i;
12136
12137     switch (gameMode) {
12138       case Training:
12139         SetTrainingModeOff();
12140         break;
12141       case MachinePlaysWhite:
12142       case MachinePlaysBlack:
12143       case BeginningOfGame:
12144         SendToProgram("force\n", &first);
12145         SetUserThinkingEnables();
12146         break;
12147       case PlayFromGameFile:
12148         (void) StopLoadGameTimer();
12149         if (gameFileFP != NULL) {
12150             gameFileFP = NULL;
12151         }
12152         break;
12153       case EditPosition:
12154         EditPositionDone(TRUE);
12155         break;
12156       case AnalyzeMode:
12157       case AnalyzeFile:
12158         ExitAnalyzeMode();
12159         SendToProgram("force\n", &first);
12160         break;
12161       case TwoMachinesPlay:
12162         GameEnds(EndOfFile, NULL, GE_PLAYER);
12163         ResurrectChessProgram();
12164         SetUserThinkingEnables();
12165         break;
12166       case EndOfGame:
12167         ResurrectChessProgram();
12168         break;
12169       case IcsPlayingBlack:
12170       case IcsPlayingWhite:
12171         DisplayError(_("Warning: You are still playing a game"), 0);
12172         break;
12173       case IcsObserving:
12174         DisplayError(_("Warning: You are still observing a game"), 0);
12175         break;
12176       case IcsExamining:
12177         DisplayError(_("Warning: You are still examining a game"), 0);
12178         break;
12179       case IcsIdle:
12180         break;
12181       case EditGame:
12182       default:
12183         return;
12184     }
12185
12186     pausing = FALSE;
12187     StopClocks();
12188     first.offeredDraw = second.offeredDraw = 0;
12189
12190     if (gameMode == PlayFromGameFile) {
12191         whiteTimeRemaining = timeRemaining[0][currentMove];
12192         blackTimeRemaining = timeRemaining[1][currentMove];
12193         DisplayTitle("");
12194     }
12195
12196     if (gameMode == MachinePlaysWhite ||
12197         gameMode == MachinePlaysBlack ||
12198         gameMode == TwoMachinesPlay ||
12199         gameMode == EndOfGame) {
12200         i = forwardMostMove;
12201         while (i > currentMove) {
12202             SendToProgram("undo\n", &first);
12203             i--;
12204         }
12205         whiteTimeRemaining = timeRemaining[0][currentMove];
12206         blackTimeRemaining = timeRemaining[1][currentMove];
12207         DisplayBothClocks();
12208         if (whiteFlag || blackFlag) {
12209             whiteFlag = blackFlag = 0;
12210         }
12211         DisplayTitle("");
12212     }
12213
12214     gameMode = EditGame;
12215     ModeHighlight();
12216     SetGameInfo();
12217 }
12218
12219
12220 void
12221 EditPositionEvent()
12222 {
12223     if (gameMode == EditPosition) {
12224         EditGameEvent();
12225         return;
12226     }
12227
12228     EditGameEvent();
12229     if (gameMode != EditGame) return;
12230
12231     gameMode = EditPosition;
12232     ModeHighlight();
12233     SetGameInfo();
12234     if (currentMove > 0)
12235       CopyBoard(boards[0], boards[currentMove]);
12236
12237     blackPlaysFirst = !WhiteOnMove(currentMove);
12238     ResetClocks();
12239     currentMove = forwardMostMove = backwardMostMove = 0;
12240     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12241     DisplayMove(-1);
12242 }
12243
12244 void
12245 ExitAnalyzeMode()
12246 {
12247     /* [DM] icsEngineAnalyze - possible call from other functions */
12248     if (appData.icsEngineAnalyze) {
12249         appData.icsEngineAnalyze = FALSE;
12250
12251         DisplayMessage("",_("Close ICS engine analyze..."));
12252     }
12253     if (first.analysisSupport && first.analyzing) {
12254       SendToProgram("exit\n", &first);
12255       first.analyzing = FALSE;
12256     }
12257     thinkOutput[0] = NULLCHAR;
12258 }
12259
12260 void
12261 EditPositionDone(Boolean fakeRights)
12262 {
12263     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12264
12265     startedFromSetupPosition = TRUE;
12266     InitChessProgram(&first, FALSE);
12267     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12268       boards[0][EP_STATUS] = EP_NONE;
12269       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12270     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12271         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12272         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12273       } else boards[0][CASTLING][2] = NoRights;
12274     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12275         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12276         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12277       } else boards[0][CASTLING][5] = NoRights;
12278     }
12279     SendToProgram("force\n", &first);
12280     if (blackPlaysFirst) {
12281         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12282         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12283         currentMove = forwardMostMove = backwardMostMove = 1;
12284         CopyBoard(boards[1], boards[0]);
12285     } else {
12286         currentMove = forwardMostMove = backwardMostMove = 0;
12287     }
12288     SendBoard(&first, forwardMostMove);
12289     if (appData.debugMode) {
12290         fprintf(debugFP, "EditPosDone\n");
12291     }
12292     DisplayTitle("");
12293     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12294     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12295     gameMode = EditGame;
12296     ModeHighlight();
12297     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12298     ClearHighlights(); /* [AS] */
12299 }
12300
12301 /* Pause for `ms' milliseconds */
12302 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12303 void
12304 TimeDelay(ms)
12305      long ms;
12306 {
12307     TimeMark m1, m2;
12308
12309     GetTimeMark(&m1);
12310     do {
12311         GetTimeMark(&m2);
12312     } while (SubtractTimeMarks(&m2, &m1) < ms);
12313 }
12314
12315 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12316 void
12317 SendMultiLineToICS(buf)
12318      char *buf;
12319 {
12320     char temp[MSG_SIZ+1], *p;
12321     int len;
12322
12323     len = strlen(buf);
12324     if (len > MSG_SIZ)
12325       len = MSG_SIZ;
12326
12327     strncpy(temp, buf, len);
12328     temp[len] = 0;
12329
12330     p = temp;
12331     while (*p) {
12332         if (*p == '\n' || *p == '\r')
12333           *p = ' ';
12334         ++p;
12335     }
12336
12337     strcat(temp, "\n");
12338     SendToICS(temp);
12339     SendToPlayer(temp, strlen(temp));
12340 }
12341
12342 void
12343 SetWhiteToPlayEvent()
12344 {
12345     if (gameMode == EditPosition) {
12346         blackPlaysFirst = FALSE;
12347         DisplayBothClocks();    /* works because currentMove is 0 */
12348     } else if (gameMode == IcsExamining) {
12349         SendToICS(ics_prefix);
12350         SendToICS("tomove white\n");
12351     }
12352 }
12353
12354 void
12355 SetBlackToPlayEvent()
12356 {
12357     if (gameMode == EditPosition) {
12358         blackPlaysFirst = TRUE;
12359         currentMove = 1;        /* kludge */
12360         DisplayBothClocks();
12361         currentMove = 0;
12362     } else if (gameMode == IcsExamining) {
12363         SendToICS(ics_prefix);
12364         SendToICS("tomove black\n");
12365     }
12366 }
12367
12368 void
12369 EditPositionMenuEvent(selection, x, y)
12370      ChessSquare selection;
12371      int x, y;
12372 {
12373     char buf[MSG_SIZ];
12374     ChessSquare piece = boards[0][y][x];
12375
12376     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12377
12378     switch (selection) {
12379       case ClearBoard:
12380         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12381             SendToICS(ics_prefix);
12382             SendToICS("bsetup clear\n");
12383         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12384             SendToICS(ics_prefix);
12385             SendToICS("clearboard\n");
12386         } else {
12387             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12388                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12389                 for (y = 0; y < BOARD_HEIGHT; y++) {
12390                     if (gameMode == IcsExamining) {
12391                         if (boards[currentMove][y][x] != EmptySquare) {
12392                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12393                                     AAA + x, ONE + y);
12394                             SendToICS(buf);
12395                         }
12396                     } else {
12397                         boards[0][y][x] = p;
12398                     }
12399                 }
12400             }
12401         }
12402         if (gameMode == EditPosition) {
12403             DrawPosition(FALSE, boards[0]);
12404         }
12405         break;
12406
12407       case WhitePlay:
12408         SetWhiteToPlayEvent();
12409         break;
12410
12411       case BlackPlay:
12412         SetBlackToPlayEvent();
12413         break;
12414
12415       case EmptySquare:
12416         if (gameMode == IcsExamining) {
12417             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12418             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12419             SendToICS(buf);
12420         } else {
12421             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12422                 if(x == BOARD_LEFT-2) {
12423                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12424                     boards[0][y][1] = 0;
12425                 } else
12426                 if(x == BOARD_RGHT+1) {
12427                     if(y >= gameInfo.holdingsSize) break;
12428                     boards[0][y][BOARD_WIDTH-2] = 0;
12429                 } else break;
12430             }
12431             boards[0][y][x] = EmptySquare;
12432             DrawPosition(FALSE, boards[0]);
12433         }
12434         break;
12435
12436       case PromotePiece:
12437         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12438            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12439             selection = (ChessSquare) (PROMOTED piece);
12440         } else if(piece == EmptySquare) selection = WhiteSilver;
12441         else selection = (ChessSquare)((int)piece - 1);
12442         goto defaultlabel;
12443
12444       case DemotePiece:
12445         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12446            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12447             selection = (ChessSquare) (DEMOTED piece);
12448         } else if(piece == EmptySquare) selection = BlackSilver;
12449         else selection = (ChessSquare)((int)piece + 1);
12450         goto defaultlabel;
12451
12452       case WhiteQueen:
12453       case BlackQueen:
12454         if(gameInfo.variant == VariantShatranj ||
12455            gameInfo.variant == VariantXiangqi  ||
12456            gameInfo.variant == VariantCourier  ||
12457            gameInfo.variant == VariantMakruk     )
12458             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12459         goto defaultlabel;
12460
12461       case WhiteKing:
12462       case BlackKing:
12463         if(gameInfo.variant == VariantXiangqi)
12464             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12465         if(gameInfo.variant == VariantKnightmate)
12466             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12467       default:
12468         defaultlabel:
12469         if (gameMode == IcsExamining) {
12470             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12471             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12472                      PieceToChar(selection), AAA + x, ONE + y);
12473             SendToICS(buf);
12474         } else {
12475             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12476                 int n;
12477                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12478                     n = PieceToNumber(selection - BlackPawn);
12479                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12480                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12481                     boards[0][BOARD_HEIGHT-1-n][1]++;
12482                 } else
12483                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12484                     n = PieceToNumber(selection);
12485                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12486                     boards[0][n][BOARD_WIDTH-1] = selection;
12487                     boards[0][n][BOARD_WIDTH-2]++;
12488                 }
12489             } else
12490             boards[0][y][x] = selection;
12491             DrawPosition(TRUE, boards[0]);
12492         }
12493         break;
12494     }
12495 }
12496
12497
12498 void
12499 DropMenuEvent(selection, x, y)
12500      ChessSquare selection;
12501      int x, y;
12502 {
12503     ChessMove moveType;
12504
12505     switch (gameMode) {
12506       case IcsPlayingWhite:
12507       case MachinePlaysBlack:
12508         if (!WhiteOnMove(currentMove)) {
12509             DisplayMoveError(_("It is Black's turn"));
12510             return;
12511         }
12512         moveType = WhiteDrop;
12513         break;
12514       case IcsPlayingBlack:
12515       case MachinePlaysWhite:
12516         if (WhiteOnMove(currentMove)) {
12517             DisplayMoveError(_("It is White's turn"));
12518             return;
12519         }
12520         moveType = BlackDrop;
12521         break;
12522       case EditGame:
12523         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12524         break;
12525       default:
12526         return;
12527     }
12528
12529     if (moveType == BlackDrop && selection < BlackPawn) {
12530       selection = (ChessSquare) ((int) selection
12531                                  + (int) BlackPawn - (int) WhitePawn);
12532     }
12533     if (boards[currentMove][y][x] != EmptySquare) {
12534         DisplayMoveError(_("That square is occupied"));
12535         return;
12536     }
12537
12538     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12539 }
12540
12541 void
12542 AcceptEvent()
12543 {
12544     /* Accept a pending offer of any kind from opponent */
12545
12546     if (appData.icsActive) {
12547         SendToICS(ics_prefix);
12548         SendToICS("accept\n");
12549     } else if (cmailMsgLoaded) {
12550         if (currentMove == cmailOldMove &&
12551             commentList[cmailOldMove] != NULL &&
12552             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12553                    "Black offers a draw" : "White offers a draw")) {
12554             TruncateGame();
12555             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12556             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12557         } else {
12558             DisplayError(_("There is no pending offer on this move"), 0);
12559             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12560         }
12561     } else {
12562         /* Not used for offers from chess program */
12563     }
12564 }
12565
12566 void
12567 DeclineEvent()
12568 {
12569     /* Decline a pending offer of any kind from opponent */
12570
12571     if (appData.icsActive) {
12572         SendToICS(ics_prefix);
12573         SendToICS("decline\n");
12574     } else if (cmailMsgLoaded) {
12575         if (currentMove == cmailOldMove &&
12576             commentList[cmailOldMove] != NULL &&
12577             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12578                    "Black offers a draw" : "White offers a draw")) {
12579 #ifdef NOTDEF
12580             AppendComment(cmailOldMove, "Draw declined", TRUE);
12581             DisplayComment(cmailOldMove - 1, "Draw declined");
12582 #endif /*NOTDEF*/
12583         } else {
12584             DisplayError(_("There is no pending offer on this move"), 0);
12585         }
12586     } else {
12587         /* Not used for offers from chess program */
12588     }
12589 }
12590
12591 void
12592 RematchEvent()
12593 {
12594     /* Issue ICS rematch command */
12595     if (appData.icsActive) {
12596         SendToICS(ics_prefix);
12597         SendToICS("rematch\n");
12598     }
12599 }
12600
12601 void
12602 CallFlagEvent()
12603 {
12604     /* Call your opponent's flag (claim a win on time) */
12605     if (appData.icsActive) {
12606         SendToICS(ics_prefix);
12607         SendToICS("flag\n");
12608     } else {
12609         switch (gameMode) {
12610           default:
12611             return;
12612           case MachinePlaysWhite:
12613             if (whiteFlag) {
12614                 if (blackFlag)
12615                   GameEnds(GameIsDrawn, "Both players ran out of time",
12616                            GE_PLAYER);
12617                 else
12618                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12619             } else {
12620                 DisplayError(_("Your opponent is not out of time"), 0);
12621             }
12622             break;
12623           case MachinePlaysBlack:
12624             if (blackFlag) {
12625                 if (whiteFlag)
12626                   GameEnds(GameIsDrawn, "Both players ran out of time",
12627                            GE_PLAYER);
12628                 else
12629                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12630             } else {
12631                 DisplayError(_("Your opponent is not out of time"), 0);
12632             }
12633             break;
12634         }
12635     }
12636 }
12637
12638 void
12639 ClockClick(int which)
12640 {       // [HGM] code moved to back-end from winboard.c
12641         if(which) { // black clock
12642           if (gameMode == EditPosition || gameMode == IcsExamining) {
12643             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12644             SetBlackToPlayEvent();
12645           } else if (gameMode == EditGame || shiftKey) {
12646             AdjustClock(which, -1);
12647           } else if (gameMode == IcsPlayingWhite ||
12648                      gameMode == MachinePlaysBlack) {
12649             CallFlagEvent();
12650           }
12651         } else { // white clock
12652           if (gameMode == EditPosition || gameMode == IcsExamining) {
12653             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12654             SetWhiteToPlayEvent();
12655           } else if (gameMode == EditGame || shiftKey) {
12656             AdjustClock(which, -1);
12657           } else if (gameMode == IcsPlayingBlack ||
12658                    gameMode == MachinePlaysWhite) {
12659             CallFlagEvent();
12660           }
12661         }
12662 }
12663
12664 void
12665 DrawEvent()
12666 {
12667     /* Offer draw or accept pending draw offer from opponent */
12668
12669     if (appData.icsActive) {
12670         /* Note: tournament rules require draw offers to be
12671            made after you make your move but before you punch
12672            your clock.  Currently ICS doesn't let you do that;
12673            instead, you immediately punch your clock after making
12674            a move, but you can offer a draw at any time. */
12675
12676         SendToICS(ics_prefix);
12677         SendToICS("draw\n");
12678         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12679     } else if (cmailMsgLoaded) {
12680         if (currentMove == cmailOldMove &&
12681             commentList[cmailOldMove] != NULL &&
12682             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12683                    "Black offers a draw" : "White offers a draw")) {
12684             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12685             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12686         } else if (currentMove == cmailOldMove + 1) {
12687             char *offer = WhiteOnMove(cmailOldMove) ?
12688               "White offers a draw" : "Black offers a draw";
12689             AppendComment(currentMove, offer, TRUE);
12690             DisplayComment(currentMove - 1, offer);
12691             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12692         } else {
12693             DisplayError(_("You must make your move before offering a draw"), 0);
12694             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12695         }
12696     } else if (first.offeredDraw) {
12697         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12698     } else {
12699         if (first.sendDrawOffers) {
12700             SendToProgram("draw\n", &first);
12701             userOfferedDraw = TRUE;
12702         }
12703     }
12704 }
12705
12706 void
12707 AdjournEvent()
12708 {
12709     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12710
12711     if (appData.icsActive) {
12712         SendToICS(ics_prefix);
12713         SendToICS("adjourn\n");
12714     } else {
12715         /* Currently GNU Chess doesn't offer or accept Adjourns */
12716     }
12717 }
12718
12719
12720 void
12721 AbortEvent()
12722 {
12723     /* Offer Abort or accept pending Abort offer from opponent */
12724
12725     if (appData.icsActive) {
12726         SendToICS(ics_prefix);
12727         SendToICS("abort\n");
12728     } else {
12729         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12730     }
12731 }
12732
12733 void
12734 ResignEvent()
12735 {
12736     /* Resign.  You can do this even if it's not your turn. */
12737
12738     if (appData.icsActive) {
12739         SendToICS(ics_prefix);
12740         SendToICS("resign\n");
12741     } else {
12742         switch (gameMode) {
12743           case MachinePlaysWhite:
12744             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12745             break;
12746           case MachinePlaysBlack:
12747             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12748             break;
12749           case EditGame:
12750             if (cmailMsgLoaded) {
12751                 TruncateGame();
12752                 if (WhiteOnMove(cmailOldMove)) {
12753                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12754                 } else {
12755                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12756                 }
12757                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12758             }
12759             break;
12760           default:
12761             break;
12762         }
12763     }
12764 }
12765
12766
12767 void
12768 StopObservingEvent()
12769 {
12770     /* Stop observing current games */
12771     SendToICS(ics_prefix);
12772     SendToICS("unobserve\n");
12773 }
12774
12775 void
12776 StopExaminingEvent()
12777 {
12778     /* Stop observing current game */
12779     SendToICS(ics_prefix);
12780     SendToICS("unexamine\n");
12781 }
12782
12783 void
12784 ForwardInner(target)
12785      int target;
12786 {
12787     int limit;
12788
12789     if (appData.debugMode)
12790         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12791                 target, currentMove, forwardMostMove);
12792
12793     if (gameMode == EditPosition)
12794       return;
12795
12796     if (gameMode == PlayFromGameFile && !pausing)
12797       PauseEvent();
12798
12799     if (gameMode == IcsExamining && pausing)
12800       limit = pauseExamForwardMostMove;
12801     else
12802       limit = forwardMostMove;
12803
12804     if (target > limit) target = limit;
12805
12806     if (target > 0 && moveList[target - 1][0]) {
12807         int fromX, fromY, toX, toY;
12808         toX = moveList[target - 1][2] - AAA;
12809         toY = moveList[target - 1][3] - ONE;
12810         if (moveList[target - 1][1] == '@') {
12811             if (appData.highlightLastMove) {
12812                 SetHighlights(-1, -1, toX, toY);
12813             }
12814         } else {
12815             fromX = moveList[target - 1][0] - AAA;
12816             fromY = moveList[target - 1][1] - ONE;
12817             if (target == currentMove + 1) {
12818                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12819             }
12820             if (appData.highlightLastMove) {
12821                 SetHighlights(fromX, fromY, toX, toY);
12822             }
12823         }
12824     }
12825     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12826         gameMode == Training || gameMode == PlayFromGameFile ||
12827         gameMode == AnalyzeFile) {
12828         while (currentMove < target) {
12829             SendMoveToProgram(currentMove++, &first);
12830         }
12831     } else {
12832         currentMove = target;
12833     }
12834
12835     if (gameMode == EditGame || gameMode == EndOfGame) {
12836         whiteTimeRemaining = timeRemaining[0][currentMove];
12837         blackTimeRemaining = timeRemaining[1][currentMove];
12838     }
12839     DisplayBothClocks();
12840     DisplayMove(currentMove - 1);
12841     DrawPosition(FALSE, boards[currentMove]);
12842     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12843     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12844         DisplayComment(currentMove - 1, commentList[currentMove]);
12845     }
12846 }
12847
12848
12849 void
12850 ForwardEvent()
12851 {
12852     if (gameMode == IcsExamining && !pausing) {
12853         SendToICS(ics_prefix);
12854         SendToICS("forward\n");
12855     } else {
12856         ForwardInner(currentMove + 1);
12857     }
12858 }
12859
12860 void
12861 ToEndEvent()
12862 {
12863     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12864         /* to optimze, we temporarily turn off analysis mode while we feed
12865          * the remaining moves to the engine. Otherwise we get analysis output
12866          * after each move.
12867          */
12868         if (first.analysisSupport) {
12869           SendToProgram("exit\nforce\n", &first);
12870           first.analyzing = FALSE;
12871         }
12872     }
12873
12874     if (gameMode == IcsExamining && !pausing) {
12875         SendToICS(ics_prefix);
12876         SendToICS("forward 999999\n");
12877     } else {
12878         ForwardInner(forwardMostMove);
12879     }
12880
12881     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12882         /* we have fed all the moves, so reactivate analysis mode */
12883         SendToProgram("analyze\n", &first);
12884         first.analyzing = TRUE;
12885         /*first.maybeThinking = TRUE;*/
12886         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12887     }
12888 }
12889
12890 void
12891 BackwardInner(target)
12892      int target;
12893 {
12894     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12895
12896     if (appData.debugMode)
12897         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12898                 target, currentMove, forwardMostMove);
12899
12900     if (gameMode == EditPosition) return;
12901     if (currentMove <= backwardMostMove) {
12902         ClearHighlights();
12903         DrawPosition(full_redraw, boards[currentMove]);
12904         return;
12905     }
12906     if (gameMode == PlayFromGameFile && !pausing)
12907       PauseEvent();
12908
12909     if (moveList[target][0]) {
12910         int fromX, fromY, toX, toY;
12911         toX = moveList[target][2] - AAA;
12912         toY = moveList[target][3] - ONE;
12913         if (moveList[target][1] == '@') {
12914             if (appData.highlightLastMove) {
12915                 SetHighlights(-1, -1, toX, toY);
12916             }
12917         } else {
12918             fromX = moveList[target][0] - AAA;
12919             fromY = moveList[target][1] - ONE;
12920             if (target == currentMove - 1) {
12921                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12922             }
12923             if (appData.highlightLastMove) {
12924                 SetHighlights(fromX, fromY, toX, toY);
12925             }
12926         }
12927     }
12928     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12929         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12930         while (currentMove > target) {
12931             SendToProgram("undo\n", &first);
12932             currentMove--;
12933         }
12934     } else {
12935         currentMove = target;
12936     }
12937
12938     if (gameMode == EditGame || gameMode == EndOfGame) {
12939         whiteTimeRemaining = timeRemaining[0][currentMove];
12940         blackTimeRemaining = timeRemaining[1][currentMove];
12941     }
12942     DisplayBothClocks();
12943     DisplayMove(currentMove - 1);
12944     DrawPosition(full_redraw, boards[currentMove]);
12945     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12946     // [HGM] PV info: routine tests if comment empty
12947     DisplayComment(currentMove - 1, commentList[currentMove]);
12948 }
12949
12950 void
12951 BackwardEvent()
12952 {
12953     if (gameMode == IcsExamining && !pausing) {
12954         SendToICS(ics_prefix);
12955         SendToICS("backward\n");
12956     } else {
12957         BackwardInner(currentMove - 1);
12958     }
12959 }
12960
12961 void
12962 ToStartEvent()
12963 {
12964     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12965         /* to optimize, we temporarily turn off analysis mode while we undo
12966          * all the moves. Otherwise we get analysis output after each undo.
12967          */
12968         if (first.analysisSupport) {
12969           SendToProgram("exit\nforce\n", &first);
12970           first.analyzing = FALSE;
12971         }
12972     }
12973
12974     if (gameMode == IcsExamining && !pausing) {
12975         SendToICS(ics_prefix);
12976         SendToICS("backward 999999\n");
12977     } else {
12978         BackwardInner(backwardMostMove);
12979     }
12980
12981     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12982         /* we have fed all the moves, so reactivate analysis mode */
12983         SendToProgram("analyze\n", &first);
12984         first.analyzing = TRUE;
12985         /*first.maybeThinking = TRUE;*/
12986         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12987     }
12988 }
12989
12990 void
12991 ToNrEvent(int to)
12992 {
12993   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12994   if (to >= forwardMostMove) to = forwardMostMove;
12995   if (to <= backwardMostMove) to = backwardMostMove;
12996   if (to < currentMove) {
12997     BackwardInner(to);
12998   } else {
12999     ForwardInner(to);
13000   }
13001 }
13002
13003 void
13004 RevertEvent(Boolean annotate)
13005 {
13006     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13007         return;
13008     }
13009     if (gameMode != IcsExamining) {
13010         DisplayError(_("You are not examining a game"), 0);
13011         return;
13012     }
13013     if (pausing) {
13014         DisplayError(_("You can't revert while pausing"), 0);
13015         return;
13016     }
13017     SendToICS(ics_prefix);
13018     SendToICS("revert\n");
13019 }
13020
13021 void
13022 RetractMoveEvent()
13023 {
13024     switch (gameMode) {
13025       case MachinePlaysWhite:
13026       case MachinePlaysBlack:
13027         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13028             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13029             return;
13030         }
13031         if (forwardMostMove < 2) return;
13032         currentMove = forwardMostMove = forwardMostMove - 2;
13033         whiteTimeRemaining = timeRemaining[0][currentMove];
13034         blackTimeRemaining = timeRemaining[1][currentMove];
13035         DisplayBothClocks();
13036         DisplayMove(currentMove - 1);
13037         ClearHighlights();/*!! could figure this out*/
13038         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13039         SendToProgram("remove\n", &first);
13040         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13041         break;
13042
13043       case BeginningOfGame:
13044       default:
13045         break;
13046
13047       case IcsPlayingWhite:
13048       case IcsPlayingBlack:
13049         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13050             SendToICS(ics_prefix);
13051             SendToICS("takeback 2\n");
13052         } else {
13053             SendToICS(ics_prefix);
13054             SendToICS("takeback 1\n");
13055         }
13056         break;
13057     }
13058 }
13059
13060 void
13061 MoveNowEvent()
13062 {
13063     ChessProgramState *cps;
13064
13065     switch (gameMode) {
13066       case MachinePlaysWhite:
13067         if (!WhiteOnMove(forwardMostMove)) {
13068             DisplayError(_("It is your turn"), 0);
13069             return;
13070         }
13071         cps = &first;
13072         break;
13073       case MachinePlaysBlack:
13074         if (WhiteOnMove(forwardMostMove)) {
13075             DisplayError(_("It is your turn"), 0);
13076             return;
13077         }
13078         cps = &first;
13079         break;
13080       case TwoMachinesPlay:
13081         if (WhiteOnMove(forwardMostMove) ==
13082             (first.twoMachinesColor[0] == 'w')) {
13083             cps = &first;
13084         } else {
13085             cps = &second;
13086         }
13087         break;
13088       case BeginningOfGame:
13089       default:
13090         return;
13091     }
13092     SendToProgram("?\n", cps);
13093 }
13094
13095 void
13096 TruncateGameEvent()
13097 {
13098     EditGameEvent();
13099     if (gameMode != EditGame) return;
13100     TruncateGame();
13101 }
13102
13103 void
13104 TruncateGame()
13105 {
13106     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13107     if (forwardMostMove > currentMove) {
13108         if (gameInfo.resultDetails != NULL) {
13109             free(gameInfo.resultDetails);
13110             gameInfo.resultDetails = NULL;
13111             gameInfo.result = GameUnfinished;
13112         }
13113         forwardMostMove = currentMove;
13114         HistorySet(parseList, backwardMostMove, forwardMostMove,
13115                    currentMove-1);
13116     }
13117 }
13118
13119 void
13120 HintEvent()
13121 {
13122     if (appData.noChessProgram) return;
13123     switch (gameMode) {
13124       case MachinePlaysWhite:
13125         if (WhiteOnMove(forwardMostMove)) {
13126             DisplayError(_("Wait until your turn"), 0);
13127             return;
13128         }
13129         break;
13130       case BeginningOfGame:
13131       case MachinePlaysBlack:
13132         if (!WhiteOnMove(forwardMostMove)) {
13133             DisplayError(_("Wait until your turn"), 0);
13134             return;
13135         }
13136         break;
13137       default:
13138         DisplayError(_("No hint available"), 0);
13139         return;
13140     }
13141     SendToProgram("hint\n", &first);
13142     hintRequested = TRUE;
13143 }
13144
13145 void
13146 BookEvent()
13147 {
13148     if (appData.noChessProgram) return;
13149     switch (gameMode) {
13150       case MachinePlaysWhite:
13151         if (WhiteOnMove(forwardMostMove)) {
13152             DisplayError(_("Wait until your turn"), 0);
13153             return;
13154         }
13155         break;
13156       case BeginningOfGame:
13157       case MachinePlaysBlack:
13158         if (!WhiteOnMove(forwardMostMove)) {
13159             DisplayError(_("Wait until your turn"), 0);
13160             return;
13161         }
13162         break;
13163       case EditPosition:
13164         EditPositionDone(TRUE);
13165         break;
13166       case TwoMachinesPlay:
13167         return;
13168       default:
13169         break;
13170     }
13171     SendToProgram("bk\n", &first);
13172     bookOutput[0] = NULLCHAR;
13173     bookRequested = TRUE;
13174 }
13175
13176 void
13177 AboutGameEvent()
13178 {
13179     char *tags = PGNTags(&gameInfo);
13180     TagsPopUp(tags, CmailMsg());
13181     free(tags);
13182 }
13183
13184 /* end button procedures */
13185
13186 void
13187 PrintPosition(fp, move)
13188      FILE *fp;
13189      int move;
13190 {
13191     int i, j;
13192
13193     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13194         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13195             char c = PieceToChar(boards[move][i][j]);
13196             fputc(c == 'x' ? '.' : c, fp);
13197             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13198         }
13199     }
13200     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13201       fprintf(fp, "white to play\n");
13202     else
13203       fprintf(fp, "black to play\n");
13204 }
13205
13206 void
13207 PrintOpponents(fp)
13208      FILE *fp;
13209 {
13210     if (gameInfo.white != NULL) {
13211         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13212     } else {
13213         fprintf(fp, "\n");
13214     }
13215 }
13216
13217 /* Find last component of program's own name, using some heuristics */
13218 void
13219 TidyProgramName(prog, host, buf)
13220      char *prog, *host, buf[MSG_SIZ];
13221 {
13222     char *p, *q;
13223     int local = (strcmp(host, "localhost") == 0);
13224     while (!local && (p = strchr(prog, ';')) != NULL) {
13225         p++;
13226         while (*p == ' ') p++;
13227         prog = p;
13228     }
13229     if (*prog == '"' || *prog == '\'') {
13230         q = strchr(prog + 1, *prog);
13231     } else {
13232         q = strchr(prog, ' ');
13233     }
13234     if (q == NULL) q = prog + strlen(prog);
13235     p = q;
13236     while (p >= prog && *p != '/' && *p != '\\') p--;
13237     p++;
13238     if(p == prog && *p == '"') p++;
13239     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13240     memcpy(buf, p, q - p);
13241     buf[q - p] = NULLCHAR;
13242     if (!local) {
13243         strcat(buf, "@");
13244         strcat(buf, host);
13245     }
13246 }
13247
13248 char *
13249 TimeControlTagValue()
13250 {
13251     char buf[MSG_SIZ];
13252     if (!appData.clockMode) {
13253       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13254     } else if (movesPerSession > 0) {
13255       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13256     } else if (timeIncrement == 0) {
13257       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13258     } else {
13259       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13260     }
13261     return StrSave(buf);
13262 }
13263
13264 void
13265 SetGameInfo()
13266 {
13267     /* This routine is used only for certain modes */
13268     VariantClass v = gameInfo.variant;
13269     ChessMove r = GameUnfinished;
13270     char *p = NULL;
13271
13272     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13273         r = gameInfo.result;
13274         p = gameInfo.resultDetails;
13275         gameInfo.resultDetails = NULL;
13276     }
13277     ClearGameInfo(&gameInfo);
13278     gameInfo.variant = v;
13279
13280     switch (gameMode) {
13281       case MachinePlaysWhite:
13282         gameInfo.event = StrSave( appData.pgnEventHeader );
13283         gameInfo.site = StrSave(HostName());
13284         gameInfo.date = PGNDate();
13285         gameInfo.round = StrSave("-");
13286         gameInfo.white = StrSave(first.tidy);
13287         gameInfo.black = StrSave(UserName());
13288         gameInfo.timeControl = TimeControlTagValue();
13289         break;
13290
13291       case MachinePlaysBlack:
13292         gameInfo.event = StrSave( appData.pgnEventHeader );
13293         gameInfo.site = StrSave(HostName());
13294         gameInfo.date = PGNDate();
13295         gameInfo.round = StrSave("-");
13296         gameInfo.white = StrSave(UserName());
13297         gameInfo.black = StrSave(first.tidy);
13298         gameInfo.timeControl = TimeControlTagValue();
13299         break;
13300
13301       case TwoMachinesPlay:
13302         gameInfo.event = StrSave( appData.pgnEventHeader );
13303         gameInfo.site = StrSave(HostName());
13304         gameInfo.date = PGNDate();
13305         if (matchGame > 0) {
13306             char buf[MSG_SIZ];
13307             snprintf(buf, MSG_SIZ, "%d", matchGame);
13308             gameInfo.round = StrSave(buf);
13309         } else {
13310             gameInfo.round = StrSave("-");
13311         }
13312         if (first.twoMachinesColor[0] == 'w') {
13313             gameInfo.white = StrSave(first.tidy);
13314             gameInfo.black = StrSave(second.tidy);
13315         } else {
13316             gameInfo.white = StrSave(second.tidy);
13317             gameInfo.black = StrSave(first.tidy);
13318         }
13319         gameInfo.timeControl = TimeControlTagValue();
13320         break;
13321
13322       case EditGame:
13323         gameInfo.event = StrSave("Edited game");
13324         gameInfo.site = StrSave(HostName());
13325         gameInfo.date = PGNDate();
13326         gameInfo.round = StrSave("-");
13327         gameInfo.white = StrSave("-");
13328         gameInfo.black = StrSave("-");
13329         gameInfo.result = r;
13330         gameInfo.resultDetails = p;
13331         break;
13332
13333       case EditPosition:
13334         gameInfo.event = StrSave("Edited position");
13335         gameInfo.site = StrSave(HostName());
13336         gameInfo.date = PGNDate();
13337         gameInfo.round = StrSave("-");
13338         gameInfo.white = StrSave("-");
13339         gameInfo.black = StrSave("-");
13340         break;
13341
13342       case IcsPlayingWhite:
13343       case IcsPlayingBlack:
13344       case IcsObserving:
13345       case IcsExamining:
13346         break;
13347
13348       case PlayFromGameFile:
13349         gameInfo.event = StrSave("Game from non-PGN file");
13350         gameInfo.site = StrSave(HostName());
13351         gameInfo.date = PGNDate();
13352         gameInfo.round = StrSave("-");
13353         gameInfo.white = StrSave("?");
13354         gameInfo.black = StrSave("?");
13355         break;
13356
13357       default:
13358         break;
13359     }
13360 }
13361
13362 void
13363 ReplaceComment(index, text)
13364      int index;
13365      char *text;
13366 {
13367     int len;
13368     char *p;
13369     float score;
13370
13371     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13372        pvInfoList[index-1].depth == len &&
13373        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13374        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13375     while (*text == '\n') text++;
13376     len = strlen(text);
13377     while (len > 0 && text[len - 1] == '\n') len--;
13378
13379     if (commentList[index] != NULL)
13380       free(commentList[index]);
13381
13382     if (len == 0) {
13383         commentList[index] = NULL;
13384         return;
13385     }
13386   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13387       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13388       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13389     commentList[index] = (char *) malloc(len + 2);
13390     strncpy(commentList[index], text, len);
13391     commentList[index][len] = '\n';
13392     commentList[index][len + 1] = NULLCHAR;
13393   } else {
13394     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13395     char *p;
13396     commentList[index] = (char *) malloc(len + 7);
13397     safeStrCpy(commentList[index], "{\n", 3);
13398     safeStrCpy(commentList[index]+2, text, len+1);
13399     commentList[index][len+2] = NULLCHAR;
13400     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13401     strcat(commentList[index], "\n}\n");
13402   }
13403 }
13404
13405 void
13406 CrushCRs(text)
13407      char *text;
13408 {
13409   char *p = text;
13410   char *q = text;
13411   char ch;
13412
13413   do {
13414     ch = *p++;
13415     if (ch == '\r') continue;
13416     *q++ = ch;
13417   } while (ch != '\0');
13418 }
13419
13420 void
13421 AppendComment(index, text, addBraces)
13422      int index;
13423      char *text;
13424      Boolean addBraces; // [HGM] braces: tells if we should add {}
13425 {
13426     int oldlen, len;
13427     char *old;
13428
13429 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13430     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13431
13432     CrushCRs(text);
13433     while (*text == '\n') text++;
13434     len = strlen(text);
13435     while (len > 0 && text[len - 1] == '\n') len--;
13436
13437     if (len == 0) return;
13438
13439     if (commentList[index] != NULL) {
13440         old = commentList[index];
13441         oldlen = strlen(old);
13442         while(commentList[index][oldlen-1] ==  '\n')
13443           commentList[index][--oldlen] = NULLCHAR;
13444         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13445         safeStrCpy(commentList[index], old, oldlen + len + 6);
13446         free(old);
13447         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13448         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13449           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13450           while (*text == '\n') { text++; len--; }
13451           commentList[index][--oldlen] = NULLCHAR;
13452       }
13453         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13454         else          strcat(commentList[index], "\n");
13455         strcat(commentList[index], text);
13456         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13457         else          strcat(commentList[index], "\n");
13458     } else {
13459         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13460         if(addBraces)
13461           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13462         else commentList[index][0] = NULLCHAR;
13463         strcat(commentList[index], text);
13464         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13465         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13466     }
13467 }
13468
13469 static char * FindStr( char * text, char * sub_text )
13470 {
13471     char * result = strstr( text, sub_text );
13472
13473     if( result != NULL ) {
13474         result += strlen( sub_text );
13475     }
13476
13477     return result;
13478 }
13479
13480 /* [AS] Try to extract PV info from PGN comment */
13481 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13482 char *GetInfoFromComment( int index, char * text )
13483 {
13484     char * sep = text, *p;
13485
13486     if( text != NULL && index > 0 ) {
13487         int score = 0;
13488         int depth = 0;
13489         int time = -1, sec = 0, deci;
13490         char * s_eval = FindStr( text, "[%eval " );
13491         char * s_emt = FindStr( text, "[%emt " );
13492
13493         if( s_eval != NULL || s_emt != NULL ) {
13494             /* New style */
13495             char delim;
13496
13497             if( s_eval != NULL ) {
13498                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13499                     return text;
13500                 }
13501
13502                 if( delim != ']' ) {
13503                     return text;
13504                 }
13505             }
13506
13507             if( s_emt != NULL ) {
13508             }
13509                 return text;
13510         }
13511         else {
13512             /* We expect something like: [+|-]nnn.nn/dd */
13513             int score_lo = 0;
13514
13515             if(*text != '{') return text; // [HGM] braces: must be normal comment
13516
13517             sep = strchr( text, '/' );
13518             if( sep == NULL || sep < (text+4) ) {
13519                 return text;
13520             }
13521
13522             p = text;
13523             if(p[1] == '(') { // comment starts with PV
13524                p = strchr(p, ')'); // locate end of PV
13525                if(p == NULL || sep < p+5) return text;
13526                // at this point we have something like "{(.*) +0.23/6 ..."
13527                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13528                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13529                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13530             }
13531             time = -1; sec = -1; deci = -1;
13532             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13533                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13534                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13535                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13536                 return text;
13537             }
13538
13539             if( score_lo < 0 || score_lo >= 100 ) {
13540                 return text;
13541             }
13542
13543             if(sec >= 0) time = 600*time + 10*sec; else
13544             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13545
13546             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13547
13548             /* [HGM] PV time: now locate end of PV info */
13549             while( *++sep >= '0' && *sep <= '9'); // strip depth
13550             if(time >= 0)
13551             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13552             if(sec >= 0)
13553             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13554             if(deci >= 0)
13555             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13556             while(*sep == ' ') sep++;
13557         }
13558
13559         if( depth <= 0 ) {
13560             return text;
13561         }
13562
13563         if( time < 0 ) {
13564             time = -1;
13565         }
13566
13567         pvInfoList[index-1].depth = depth;
13568         pvInfoList[index-1].score = score;
13569         pvInfoList[index-1].time  = 10*time; // centi-sec
13570         if(*sep == '}') *sep = 0; else *--sep = '{';
13571         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13572     }
13573     return sep;
13574 }
13575
13576 void
13577 SendToProgram(message, cps)
13578      char *message;
13579      ChessProgramState *cps;
13580 {
13581     int count, outCount, error;
13582     char buf[MSG_SIZ];
13583
13584     if (cps->pr == NULL) return;
13585     Attention(cps);
13586
13587     if (appData.debugMode) {
13588         TimeMark now;
13589         GetTimeMark(&now);
13590         fprintf(debugFP, "%ld >%-6s: %s",
13591                 SubtractTimeMarks(&now, &programStartTime),
13592                 cps->which, message);
13593     }
13594
13595     count = strlen(message);
13596     outCount = OutputToProcess(cps->pr, message, count, &error);
13597     if (outCount < count && !exiting
13598                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13599       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13600         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13601             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13602                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13603                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13604             } else {
13605                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13606             }
13607             gameInfo.resultDetails = StrSave(buf);
13608         }
13609         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13610     }
13611 }
13612
13613 void
13614 ReceiveFromProgram(isr, closure, message, count, error)
13615      InputSourceRef isr;
13616      VOIDSTAR closure;
13617      char *message;
13618      int count;
13619      int error;
13620 {
13621     char *end_str;
13622     char buf[MSG_SIZ];
13623     ChessProgramState *cps = (ChessProgramState *)closure;
13624
13625     if (isr != cps->isr) return; /* Killed intentionally */
13626     if (count <= 0) {
13627         if (count == 0) {
13628             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13629                     _(cps->which), cps->program);
13630         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13631                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13632                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13633                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13634                 } else {
13635                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13636                 }
13637                 gameInfo.resultDetails = StrSave(buf);
13638             }
13639             RemoveInputSource(cps->isr);
13640             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13641         } else {
13642             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13643                     _(cps->which), cps->program);
13644             RemoveInputSource(cps->isr);
13645
13646             /* [AS] Program is misbehaving badly... kill it */
13647             if( count == -2 ) {
13648                 DestroyChildProcess( cps->pr, 9 );
13649                 cps->pr = NoProc;
13650             }
13651
13652             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13653         }
13654         return;
13655     }
13656
13657     if ((end_str = strchr(message, '\r')) != NULL)
13658       *end_str = NULLCHAR;
13659     if ((end_str = strchr(message, '\n')) != NULL)
13660       *end_str = NULLCHAR;
13661
13662     if (appData.debugMode) {
13663         TimeMark now; int print = 1;
13664         char *quote = ""; char c; int i;
13665
13666         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13667                 char start = message[0];
13668                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13669                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13670                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13671                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13672                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13673                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13674                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13675                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13676                    sscanf(message, "hint: %c", &c)!=1 && 
13677                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13678                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13679                     print = (appData.engineComments >= 2);
13680                 }
13681                 message[0] = start; // restore original message
13682         }
13683         if(print) {
13684                 GetTimeMark(&now);
13685                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13686                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13687                         quote,
13688                         message);
13689         }
13690     }
13691
13692     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13693     if (appData.icsEngineAnalyze) {
13694         if (strstr(message, "whisper") != NULL ||
13695              strstr(message, "kibitz") != NULL ||
13696             strstr(message, "tellics") != NULL) return;
13697     }
13698
13699     HandleMachineMove(message, cps);
13700 }
13701
13702
13703 void
13704 SendTimeControl(cps, mps, tc, inc, sd, st)
13705      ChessProgramState *cps;
13706      int mps, inc, sd, st;
13707      long tc;
13708 {
13709     char buf[MSG_SIZ];
13710     int seconds;
13711
13712     if( timeControl_2 > 0 ) {
13713         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13714             tc = timeControl_2;
13715         }
13716     }
13717     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13718     inc /= cps->timeOdds;
13719     st  /= cps->timeOdds;
13720
13721     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13722
13723     if (st > 0) {
13724       /* Set exact time per move, normally using st command */
13725       if (cps->stKludge) {
13726         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13727         seconds = st % 60;
13728         if (seconds == 0) {
13729           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13730         } else {
13731           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13732         }
13733       } else {
13734         snprintf(buf, MSG_SIZ, "st %d\n", st);
13735       }
13736     } else {
13737       /* Set conventional or incremental time control, using level command */
13738       if (seconds == 0) {
13739         /* Note old gnuchess bug -- minutes:seconds used to not work.
13740            Fixed in later versions, but still avoid :seconds
13741            when seconds is 0. */
13742         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13743       } else {
13744         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13745                  seconds, inc/1000.);
13746       }
13747     }
13748     SendToProgram(buf, cps);
13749
13750     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13751     /* Orthogonally, limit search to given depth */
13752     if (sd > 0) {
13753       if (cps->sdKludge) {
13754         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13755       } else {
13756         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13757       }
13758       SendToProgram(buf, cps);
13759     }
13760
13761     if(cps->nps >= 0) { /* [HGM] nps */
13762         if(cps->supportsNPS == FALSE)
13763           cps->nps = -1; // don't use if engine explicitly says not supported!
13764         else {
13765           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13766           SendToProgram(buf, cps);
13767         }
13768     }
13769 }
13770
13771 ChessProgramState *WhitePlayer()
13772 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13773 {
13774     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13775        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13776         return &second;
13777     return &first;
13778 }
13779
13780 void
13781 SendTimeRemaining(cps, machineWhite)
13782      ChessProgramState *cps;
13783      int /*boolean*/ machineWhite;
13784 {
13785     char message[MSG_SIZ];
13786     long time, otime;
13787
13788     /* Note: this routine must be called when the clocks are stopped
13789        or when they have *just* been set or switched; otherwise
13790        it will be off by the time since the current tick started.
13791     */
13792     if (machineWhite) {
13793         time = whiteTimeRemaining / 10;
13794         otime = blackTimeRemaining / 10;
13795     } else {
13796         time = blackTimeRemaining / 10;
13797         otime = whiteTimeRemaining / 10;
13798     }
13799     /* [HGM] translate opponent's time by time-odds factor */
13800     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13801     if (appData.debugMode) {
13802         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13803     }
13804
13805     if (time <= 0) time = 1;
13806     if (otime <= 0) otime = 1;
13807
13808     snprintf(message, MSG_SIZ, "time %ld\n", time);
13809     SendToProgram(message, cps);
13810
13811     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13812     SendToProgram(message, cps);
13813 }
13814
13815 int
13816 BoolFeature(p, name, loc, cps)
13817      char **p;
13818      char *name;
13819      int *loc;
13820      ChessProgramState *cps;
13821 {
13822   char buf[MSG_SIZ];
13823   int len = strlen(name);
13824   int val;
13825
13826   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13827     (*p) += len + 1;
13828     sscanf(*p, "%d", &val);
13829     *loc = (val != 0);
13830     while (**p && **p != ' ')
13831       (*p)++;
13832     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13833     SendToProgram(buf, cps);
13834     return TRUE;
13835   }
13836   return FALSE;
13837 }
13838
13839 int
13840 IntFeature(p, name, loc, cps)
13841      char **p;
13842      char *name;
13843      int *loc;
13844      ChessProgramState *cps;
13845 {
13846   char buf[MSG_SIZ];
13847   int len = strlen(name);
13848   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13849     (*p) += len + 1;
13850     sscanf(*p, "%d", loc);
13851     while (**p && **p != ' ') (*p)++;
13852     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13853     SendToProgram(buf, cps);
13854     return TRUE;
13855   }
13856   return FALSE;
13857 }
13858
13859 int
13860 StringFeature(p, name, loc, cps)
13861      char **p;
13862      char *name;
13863      char loc[];
13864      ChessProgramState *cps;
13865 {
13866   char buf[MSG_SIZ];
13867   int len = strlen(name);
13868   if (strncmp((*p), name, len) == 0
13869       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13870     (*p) += len + 2;
13871     sscanf(*p, "%[^\"]", loc);
13872     while (**p && **p != '\"') (*p)++;
13873     if (**p == '\"') (*p)++;
13874     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13875     SendToProgram(buf, cps);
13876     return TRUE;
13877   }
13878   return FALSE;
13879 }
13880
13881 int
13882 ParseOption(Option *opt, ChessProgramState *cps)
13883 // [HGM] options: process the string that defines an engine option, and determine
13884 // name, type, default value, and allowed value range
13885 {
13886         char *p, *q, buf[MSG_SIZ];
13887         int n, min = (-1)<<31, max = 1<<31, def;
13888
13889         if(p = strstr(opt->name, " -spin ")) {
13890             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13891             if(max < min) max = min; // enforce consistency
13892             if(def < min) def = min;
13893             if(def > max) def = max;
13894             opt->value = def;
13895             opt->min = min;
13896             opt->max = max;
13897             opt->type = Spin;
13898         } else if((p = strstr(opt->name, " -slider "))) {
13899             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13900             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13901             if(max < min) max = min; // enforce consistency
13902             if(def < min) def = min;
13903             if(def > max) def = max;
13904             opt->value = def;
13905             opt->min = min;
13906             opt->max = max;
13907             opt->type = Spin; // Slider;
13908         } else if((p = strstr(opt->name, " -string "))) {
13909             opt->textValue = p+9;
13910             opt->type = TextBox;
13911         } else if((p = strstr(opt->name, " -file "))) {
13912             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13913             opt->textValue = p+7;
13914             opt->type = FileName; // FileName;
13915         } else if((p = strstr(opt->name, " -path "))) {
13916             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13917             opt->textValue = p+7;
13918             opt->type = PathName; // PathName;
13919         } else if(p = strstr(opt->name, " -check ")) {
13920             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13921             opt->value = (def != 0);
13922             opt->type = CheckBox;
13923         } else if(p = strstr(opt->name, " -combo ")) {
13924             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13925             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13926             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13927             opt->value = n = 0;
13928             while(q = StrStr(q, " /// ")) {
13929                 n++; *q = 0;    // count choices, and null-terminate each of them
13930                 q += 5;
13931                 if(*q == '*') { // remember default, which is marked with * prefix
13932                     q++;
13933                     opt->value = n;
13934                 }
13935                 cps->comboList[cps->comboCnt++] = q;
13936             }
13937             cps->comboList[cps->comboCnt++] = NULL;
13938             opt->max = n + 1;
13939             opt->type = ComboBox;
13940         } else if(p = strstr(opt->name, " -button")) {
13941             opt->type = Button;
13942         } else if(p = strstr(opt->name, " -save")) {
13943             opt->type = SaveButton;
13944         } else return FALSE;
13945         *p = 0; // terminate option name
13946         // now look if the command-line options define a setting for this engine option.
13947         if(cps->optionSettings && cps->optionSettings[0])
13948             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13949         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13950           snprintf(buf, MSG_SIZ, "option %s", p);
13951                 if(p = strstr(buf, ",")) *p = 0;
13952                 if(q = strchr(buf, '=')) switch(opt->type) {
13953                     case ComboBox:
13954                         for(n=0; n<opt->max; n++)
13955                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13956                         break;
13957                     case TextBox:
13958                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13959                         break;
13960                     case Spin:
13961                     case CheckBox:
13962                         opt->value = atoi(q+1);
13963                     default:
13964                         break;
13965                 }
13966                 strcat(buf, "\n");
13967                 SendToProgram(buf, cps);
13968         }
13969         return TRUE;
13970 }
13971
13972 void
13973 FeatureDone(cps, val)
13974      ChessProgramState* cps;
13975      int val;
13976 {
13977   DelayedEventCallback cb = GetDelayedEvent();
13978   if ((cb == InitBackEnd3 && cps == &first) ||
13979       (cb == SettingsMenuIfReady && cps == &second) ||
13980       (cb == TwoMachinesEventIfReady && cps == &second)) {
13981     CancelDelayedEvent();
13982     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13983   }
13984   cps->initDone = val;
13985 }
13986
13987 /* Parse feature command from engine */
13988 void
13989 ParseFeatures(args, cps)
13990      char* args;
13991      ChessProgramState *cps;
13992 {
13993   char *p = args;
13994   char *q;
13995   int val;
13996   char buf[MSG_SIZ];
13997
13998   for (;;) {
13999     while (*p == ' ') p++;
14000     if (*p == NULLCHAR) return;
14001
14002     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14003     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14004     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14005     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14006     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14007     if (BoolFeature(&p, "reuse", &val, cps)) {
14008       /* Engine can disable reuse, but can't enable it if user said no */
14009       if (!val) cps->reuse = FALSE;
14010       continue;
14011     }
14012     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14013     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14014       if (gameMode == TwoMachinesPlay) {
14015         DisplayTwoMachinesTitle();
14016       } else {
14017         DisplayTitle("");
14018       }
14019       continue;
14020     }
14021     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14022     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14023     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14024     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14025     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14026     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14027     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14028     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14029     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14030     if (IntFeature(&p, "done", &val, cps)) {
14031       FeatureDone(cps, val);
14032       continue;
14033     }
14034     /* Added by Tord: */
14035     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14036     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14037     /* End of additions by Tord */
14038
14039     /* [HGM] added features: */
14040     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14041     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14042     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14043     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14044     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14045     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14046     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14047         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14048           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14049             SendToProgram(buf, cps);
14050             continue;
14051         }
14052         if(cps->nrOptions >= MAX_OPTIONS) {
14053             cps->nrOptions--;
14054             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14055             DisplayError(buf, 0);
14056         }
14057         continue;
14058     }
14059     /* End of additions by HGM */
14060
14061     /* unknown feature: complain and skip */
14062     q = p;
14063     while (*q && *q != '=') q++;
14064     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14065     SendToProgram(buf, cps);
14066     p = q;
14067     if (*p == '=') {
14068       p++;
14069       if (*p == '\"') {
14070         p++;
14071         while (*p && *p != '\"') p++;
14072         if (*p == '\"') p++;
14073       } else {
14074         while (*p && *p != ' ') p++;
14075       }
14076     }
14077   }
14078
14079 }
14080
14081 void
14082 PeriodicUpdatesEvent(newState)
14083      int newState;
14084 {
14085     if (newState == appData.periodicUpdates)
14086       return;
14087
14088     appData.periodicUpdates=newState;
14089
14090     /* Display type changes, so update it now */
14091 //    DisplayAnalysis();
14092
14093     /* Get the ball rolling again... */
14094     if (newState) {
14095         AnalysisPeriodicEvent(1);
14096         StartAnalysisClock();
14097     }
14098 }
14099
14100 void
14101 PonderNextMoveEvent(newState)
14102      int newState;
14103 {
14104     if (newState == appData.ponderNextMove) return;
14105     if (gameMode == EditPosition) EditPositionDone(TRUE);
14106     if (newState) {
14107         SendToProgram("hard\n", &first);
14108         if (gameMode == TwoMachinesPlay) {
14109             SendToProgram("hard\n", &second);
14110         }
14111     } else {
14112         SendToProgram("easy\n", &first);
14113         thinkOutput[0] = NULLCHAR;
14114         if (gameMode == TwoMachinesPlay) {
14115             SendToProgram("easy\n", &second);
14116         }
14117     }
14118     appData.ponderNextMove = newState;
14119 }
14120
14121 void
14122 NewSettingEvent(option, feature, command, value)
14123      char *command;
14124      int option, value, *feature;
14125 {
14126     char buf[MSG_SIZ];
14127
14128     if (gameMode == EditPosition) EditPositionDone(TRUE);
14129     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14130     if(feature == NULL || *feature) SendToProgram(buf, &first);
14131     if (gameMode == TwoMachinesPlay) {
14132         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14133     }
14134 }
14135
14136 void
14137 ShowThinkingEvent()
14138 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14139 {
14140     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14141     int newState = appData.showThinking
14142         // [HGM] thinking: other features now need thinking output as well
14143         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14144
14145     if (oldState == newState) return;
14146     oldState = newState;
14147     if (gameMode == EditPosition) EditPositionDone(TRUE);
14148     if (oldState) {
14149         SendToProgram("post\n", &first);
14150         if (gameMode == TwoMachinesPlay) {
14151             SendToProgram("post\n", &second);
14152         }
14153     } else {
14154         SendToProgram("nopost\n", &first);
14155         thinkOutput[0] = NULLCHAR;
14156         if (gameMode == TwoMachinesPlay) {
14157             SendToProgram("nopost\n", &second);
14158         }
14159     }
14160 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14161 }
14162
14163 void
14164 AskQuestionEvent(title, question, replyPrefix, which)
14165      char *title; char *question; char *replyPrefix; char *which;
14166 {
14167   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14168   if (pr == NoProc) return;
14169   AskQuestion(title, question, replyPrefix, pr);
14170 }
14171
14172 void
14173 DisplayMove(moveNumber)
14174      int moveNumber;
14175 {
14176     char message[MSG_SIZ];
14177     char res[MSG_SIZ];
14178     char cpThinkOutput[MSG_SIZ];
14179
14180     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14181
14182     if (moveNumber == forwardMostMove - 1 ||
14183         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14184
14185         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14186
14187         if (strchr(cpThinkOutput, '\n')) {
14188             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14189         }
14190     } else {
14191         *cpThinkOutput = NULLCHAR;
14192     }
14193
14194     /* [AS] Hide thinking from human user */
14195     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14196         *cpThinkOutput = NULLCHAR;
14197         if( thinkOutput[0] != NULLCHAR ) {
14198             int i;
14199
14200             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14201                 cpThinkOutput[i] = '.';
14202             }
14203             cpThinkOutput[i] = NULLCHAR;
14204             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14205         }
14206     }
14207
14208     if (moveNumber == forwardMostMove - 1 &&
14209         gameInfo.resultDetails != NULL) {
14210         if (gameInfo.resultDetails[0] == NULLCHAR) {
14211           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14212         } else {
14213           snprintf(res, MSG_SIZ, " {%s} %s",
14214                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14215         }
14216     } else {
14217         res[0] = NULLCHAR;
14218     }
14219
14220     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14221         DisplayMessage(res, cpThinkOutput);
14222     } else {
14223       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14224                 WhiteOnMove(moveNumber) ? " " : ".. ",
14225                 parseList[moveNumber], res);
14226         DisplayMessage(message, cpThinkOutput);
14227     }
14228 }
14229
14230 void
14231 DisplayComment(moveNumber, text)
14232      int moveNumber;
14233      char *text;
14234 {
14235     char title[MSG_SIZ];
14236     char buf[8000]; // comment can be long!
14237     int score, depth;
14238
14239     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14240       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14241     } else {
14242       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14243               WhiteOnMove(moveNumber) ? " " : ".. ",
14244               parseList[moveNumber]);
14245     }
14246     // [HGM] PV info: display PV info together with (or as) comment
14247     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14248       if(text == NULL) text = "";
14249       score = pvInfoList[moveNumber].score;
14250       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14251               depth, (pvInfoList[moveNumber].time+50)/100, text);
14252       text = buf;
14253     }
14254     if (text != NULL && (appData.autoDisplayComment || commentUp))
14255         CommentPopUp(title, text);
14256 }
14257
14258 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14259  * might be busy thinking or pondering.  It can be omitted if your
14260  * gnuchess is configured to stop thinking immediately on any user
14261  * input.  However, that gnuchess feature depends on the FIONREAD
14262  * ioctl, which does not work properly on some flavors of Unix.
14263  */
14264 void
14265 Attention(cps)
14266      ChessProgramState *cps;
14267 {
14268 #if ATTENTION
14269     if (!cps->useSigint) return;
14270     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14271     switch (gameMode) {
14272       case MachinePlaysWhite:
14273       case MachinePlaysBlack:
14274       case TwoMachinesPlay:
14275       case IcsPlayingWhite:
14276       case IcsPlayingBlack:
14277       case AnalyzeMode:
14278       case AnalyzeFile:
14279         /* Skip if we know it isn't thinking */
14280         if (!cps->maybeThinking) return;
14281         if (appData.debugMode)
14282           fprintf(debugFP, "Interrupting %s\n", cps->which);
14283         InterruptChildProcess(cps->pr);
14284         cps->maybeThinking = FALSE;
14285         break;
14286       default:
14287         break;
14288     }
14289 #endif /*ATTENTION*/
14290 }
14291
14292 int
14293 CheckFlags()
14294 {
14295     if (whiteTimeRemaining <= 0) {
14296         if (!whiteFlag) {
14297             whiteFlag = TRUE;
14298             if (appData.icsActive) {
14299                 if (appData.autoCallFlag &&
14300                     gameMode == IcsPlayingBlack && !blackFlag) {
14301                   SendToICS(ics_prefix);
14302                   SendToICS("flag\n");
14303                 }
14304             } else {
14305                 if (blackFlag) {
14306                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14307                 } else {
14308                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14309                     if (appData.autoCallFlag) {
14310                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14311                         return TRUE;
14312                     }
14313                 }
14314             }
14315         }
14316     }
14317     if (blackTimeRemaining <= 0) {
14318         if (!blackFlag) {
14319             blackFlag = TRUE;
14320             if (appData.icsActive) {
14321                 if (appData.autoCallFlag &&
14322                     gameMode == IcsPlayingWhite && !whiteFlag) {
14323                   SendToICS(ics_prefix);
14324                   SendToICS("flag\n");
14325                 }
14326             } else {
14327                 if (whiteFlag) {
14328                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14329                 } else {
14330                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14331                     if (appData.autoCallFlag) {
14332                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14333                         return TRUE;
14334                     }
14335                 }
14336             }
14337         }
14338     }
14339     return FALSE;
14340 }
14341
14342 void
14343 CheckTimeControl()
14344 {
14345     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14346         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14347
14348     /*
14349      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14350      */
14351     if ( !WhiteOnMove(forwardMostMove) ) {
14352         /* White made time control */
14353         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14354         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14355         /* [HGM] time odds: correct new time quota for time odds! */
14356                                             / WhitePlayer()->timeOdds;
14357         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14358     } else {
14359         lastBlack -= blackTimeRemaining;
14360         /* Black made time control */
14361         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14362                                             / WhitePlayer()->other->timeOdds;
14363         lastWhite = whiteTimeRemaining;
14364     }
14365 }
14366
14367 void
14368 DisplayBothClocks()
14369 {
14370     int wom = gameMode == EditPosition ?
14371       !blackPlaysFirst : WhiteOnMove(currentMove);
14372     DisplayWhiteClock(whiteTimeRemaining, wom);
14373     DisplayBlackClock(blackTimeRemaining, !wom);
14374 }
14375
14376
14377 /* Timekeeping seems to be a portability nightmare.  I think everyone
14378    has ftime(), but I'm really not sure, so I'm including some ifdefs
14379    to use other calls if you don't.  Clocks will be less accurate if
14380    you have neither ftime nor gettimeofday.
14381 */
14382
14383 /* VS 2008 requires the #include outside of the function */
14384 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14385 #include <sys/timeb.h>
14386 #endif
14387
14388 /* Get the current time as a TimeMark */
14389 void
14390 GetTimeMark(tm)
14391      TimeMark *tm;
14392 {
14393 #if HAVE_GETTIMEOFDAY
14394
14395     struct timeval timeVal;
14396     struct timezone timeZone;
14397
14398     gettimeofday(&timeVal, &timeZone);
14399     tm->sec = (long) timeVal.tv_sec;
14400     tm->ms = (int) (timeVal.tv_usec / 1000L);
14401
14402 #else /*!HAVE_GETTIMEOFDAY*/
14403 #if HAVE_FTIME
14404
14405 // include <sys/timeb.h> / moved to just above start of function
14406     struct timeb timeB;
14407
14408     ftime(&timeB);
14409     tm->sec = (long) timeB.time;
14410     tm->ms = (int) timeB.millitm;
14411
14412 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14413     tm->sec = (long) time(NULL);
14414     tm->ms = 0;
14415 #endif
14416 #endif
14417 }
14418
14419 /* Return the difference in milliseconds between two
14420    time marks.  We assume the difference will fit in a long!
14421 */
14422 long
14423 SubtractTimeMarks(tm2, tm1)
14424      TimeMark *tm2, *tm1;
14425 {
14426     return 1000L*(tm2->sec - tm1->sec) +
14427            (long) (tm2->ms - tm1->ms);
14428 }
14429
14430
14431 /*
14432  * Code to manage the game clocks.
14433  *
14434  * In tournament play, black starts the clock and then white makes a move.
14435  * We give the human user a slight advantage if he is playing white---the
14436  * clocks don't run until he makes his first move, so it takes zero time.
14437  * Also, we don't account for network lag, so we could get out of sync
14438  * with GNU Chess's clock -- but then, referees are always right.
14439  */
14440
14441 static TimeMark tickStartTM;
14442 static long intendedTickLength;
14443
14444 long
14445 NextTickLength(timeRemaining)
14446      long timeRemaining;
14447 {
14448     long nominalTickLength, nextTickLength;
14449
14450     if (timeRemaining > 0L && timeRemaining <= 10000L)
14451       nominalTickLength = 100L;
14452     else
14453       nominalTickLength = 1000L;
14454     nextTickLength = timeRemaining % nominalTickLength;
14455     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14456
14457     return nextTickLength;
14458 }
14459
14460 /* Adjust clock one minute up or down */
14461 void
14462 AdjustClock(Boolean which, int dir)
14463 {
14464     if(which) blackTimeRemaining += 60000*dir;
14465     else      whiteTimeRemaining += 60000*dir;
14466     DisplayBothClocks();
14467 }
14468
14469 /* Stop clocks and reset to a fresh time control */
14470 void
14471 ResetClocks()
14472 {
14473     (void) StopClockTimer();
14474     if (appData.icsActive) {
14475         whiteTimeRemaining = blackTimeRemaining = 0;
14476     } else if (searchTime) {
14477         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14478         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14479     } else { /* [HGM] correct new time quote for time odds */
14480         whiteTC = blackTC = fullTimeControlString;
14481         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14482         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14483     }
14484     if (whiteFlag || blackFlag) {
14485         DisplayTitle("");
14486         whiteFlag = blackFlag = FALSE;
14487     }
14488     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14489     DisplayBothClocks();
14490 }
14491
14492 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14493
14494 /* Decrement running clock by amount of time that has passed */
14495 void
14496 DecrementClocks()
14497 {
14498     long timeRemaining;
14499     long lastTickLength, fudge;
14500     TimeMark now;
14501
14502     if (!appData.clockMode) return;
14503     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14504
14505     GetTimeMark(&now);
14506
14507     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14508
14509     /* Fudge if we woke up a little too soon */
14510     fudge = intendedTickLength - lastTickLength;
14511     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14512
14513     if (WhiteOnMove(forwardMostMove)) {
14514         if(whiteNPS >= 0) lastTickLength = 0;
14515         timeRemaining = whiteTimeRemaining -= lastTickLength;
14516         if(timeRemaining < 0 && !appData.icsActive) {
14517             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14518             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14519                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14520                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14521             }
14522         }
14523         DisplayWhiteClock(whiteTimeRemaining - fudge,
14524                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14525     } else {
14526         if(blackNPS >= 0) lastTickLength = 0;
14527         timeRemaining = blackTimeRemaining -= lastTickLength;
14528         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14529             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14530             if(suddenDeath) {
14531                 blackStartMove = forwardMostMove;
14532                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14533             }
14534         }
14535         DisplayBlackClock(blackTimeRemaining - fudge,
14536                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14537     }
14538     if (CheckFlags()) return;
14539
14540     tickStartTM = now;
14541     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14542     StartClockTimer(intendedTickLength);
14543
14544     /* if the time remaining has fallen below the alarm threshold, sound the
14545      * alarm. if the alarm has sounded and (due to a takeback or time control
14546      * with increment) the time remaining has increased to a level above the
14547      * threshold, reset the alarm so it can sound again.
14548      */
14549
14550     if (appData.icsActive && appData.icsAlarm) {
14551
14552         /* make sure we are dealing with the user's clock */
14553         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14554                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14555            )) return;
14556
14557         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14558             alarmSounded = FALSE;
14559         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14560             PlayAlarmSound();
14561             alarmSounded = TRUE;
14562         }
14563     }
14564 }
14565
14566
14567 /* A player has just moved, so stop the previously running
14568    clock and (if in clock mode) start the other one.
14569    We redisplay both clocks in case we're in ICS mode, because
14570    ICS gives us an update to both clocks after every move.
14571    Note that this routine is called *after* forwardMostMove
14572    is updated, so the last fractional tick must be subtracted
14573    from the color that is *not* on move now.
14574 */
14575 void
14576 SwitchClocks(int newMoveNr)
14577 {
14578     long lastTickLength;
14579     TimeMark now;
14580     int flagged = FALSE;
14581
14582     GetTimeMark(&now);
14583
14584     if (StopClockTimer() && appData.clockMode) {
14585         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14586         if (!WhiteOnMove(forwardMostMove)) {
14587             if(blackNPS >= 0) lastTickLength = 0;
14588             blackTimeRemaining -= lastTickLength;
14589            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14590 //         if(pvInfoList[forwardMostMove].time == -1)
14591                  pvInfoList[forwardMostMove].time =               // use GUI time
14592                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14593         } else {
14594            if(whiteNPS >= 0) lastTickLength = 0;
14595            whiteTimeRemaining -= lastTickLength;
14596            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14597 //         if(pvInfoList[forwardMostMove].time == -1)
14598                  pvInfoList[forwardMostMove].time =
14599                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14600         }
14601         flagged = CheckFlags();
14602     }
14603     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14604     CheckTimeControl();
14605
14606     if (flagged || !appData.clockMode) return;
14607
14608     switch (gameMode) {
14609       case MachinePlaysBlack:
14610       case MachinePlaysWhite:
14611       case BeginningOfGame:
14612         if (pausing) return;
14613         break;
14614
14615       case EditGame:
14616       case PlayFromGameFile:
14617       case IcsExamining:
14618         return;
14619
14620       default:
14621         break;
14622     }
14623
14624     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14625         if(WhiteOnMove(forwardMostMove))
14626              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14627         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14628     }
14629
14630     tickStartTM = now;
14631     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14632       whiteTimeRemaining : blackTimeRemaining);
14633     StartClockTimer(intendedTickLength);
14634 }
14635
14636
14637 /* Stop both clocks */
14638 void
14639 StopClocks()
14640 {
14641     long lastTickLength;
14642     TimeMark now;
14643
14644     if (!StopClockTimer()) return;
14645     if (!appData.clockMode) return;
14646
14647     GetTimeMark(&now);
14648
14649     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14650     if (WhiteOnMove(forwardMostMove)) {
14651         if(whiteNPS >= 0) lastTickLength = 0;
14652         whiteTimeRemaining -= lastTickLength;
14653         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14654     } else {
14655         if(blackNPS >= 0) lastTickLength = 0;
14656         blackTimeRemaining -= lastTickLength;
14657         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14658     }
14659     CheckFlags();
14660 }
14661
14662 /* Start clock of player on move.  Time may have been reset, so
14663    if clock is already running, stop and restart it. */
14664 void
14665 StartClocks()
14666 {
14667     (void) StopClockTimer(); /* in case it was running already */
14668     DisplayBothClocks();
14669     if (CheckFlags()) return;
14670
14671     if (!appData.clockMode) return;
14672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14673
14674     GetTimeMark(&tickStartTM);
14675     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14676       whiteTimeRemaining : blackTimeRemaining);
14677
14678    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14679     whiteNPS = blackNPS = -1;
14680     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14681        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14682         whiteNPS = first.nps;
14683     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14684        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14685         blackNPS = first.nps;
14686     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14687         whiteNPS = second.nps;
14688     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14689         blackNPS = second.nps;
14690     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14691
14692     StartClockTimer(intendedTickLength);
14693 }
14694
14695 char *
14696 TimeString(ms)
14697      long ms;
14698 {
14699     long second, minute, hour, day;
14700     char *sign = "";
14701     static char buf[32];
14702
14703     if (ms > 0 && ms <= 9900) {
14704       /* convert milliseconds to tenths, rounding up */
14705       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14706
14707       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14708       return buf;
14709     }
14710
14711     /* convert milliseconds to seconds, rounding up */
14712     /* use floating point to avoid strangeness of integer division
14713        with negative dividends on many machines */
14714     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14715
14716     if (second < 0) {
14717         sign = "-";
14718         second = -second;
14719     }
14720
14721     day = second / (60 * 60 * 24);
14722     second = second % (60 * 60 * 24);
14723     hour = second / (60 * 60);
14724     second = second % (60 * 60);
14725     minute = second / 60;
14726     second = second % 60;
14727
14728     if (day > 0)
14729       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14730               sign, day, hour, minute, second);
14731     else if (hour > 0)
14732       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14733     else
14734       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14735
14736     return buf;
14737 }
14738
14739
14740 /*
14741  * This is necessary because some C libraries aren't ANSI C compliant yet.
14742  */
14743 char *
14744 StrStr(string, match)
14745      char *string, *match;
14746 {
14747     int i, length;
14748
14749     length = strlen(match);
14750
14751     for (i = strlen(string) - length; i >= 0; i--, string++)
14752       if (!strncmp(match, string, length))
14753         return string;
14754
14755     return NULL;
14756 }
14757
14758 char *
14759 StrCaseStr(string, match)
14760      char *string, *match;
14761 {
14762     int i, j, length;
14763
14764     length = strlen(match);
14765
14766     for (i = strlen(string) - length; i >= 0; i--, string++) {
14767         for (j = 0; j < length; j++) {
14768             if (ToLower(match[j]) != ToLower(string[j]))
14769               break;
14770         }
14771         if (j == length) return string;
14772     }
14773
14774     return NULL;
14775 }
14776
14777 #ifndef _amigados
14778 int
14779 StrCaseCmp(s1, s2)
14780      char *s1, *s2;
14781 {
14782     char c1, c2;
14783
14784     for (;;) {
14785         c1 = ToLower(*s1++);
14786         c2 = ToLower(*s2++);
14787         if (c1 > c2) return 1;
14788         if (c1 < c2) return -1;
14789         if (c1 == NULLCHAR) return 0;
14790     }
14791 }
14792
14793
14794 int
14795 ToLower(c)
14796      int c;
14797 {
14798     return isupper(c) ? tolower(c) : c;
14799 }
14800
14801
14802 int
14803 ToUpper(c)
14804      int c;
14805 {
14806     return islower(c) ? toupper(c) : c;
14807 }
14808 #endif /* !_amigados    */
14809
14810 char *
14811 StrSave(s)
14812      char *s;
14813 {
14814   char *ret;
14815
14816   if ((ret = (char *) malloc(strlen(s) + 1)))
14817     {
14818       safeStrCpy(ret, s, strlen(s)+1);
14819     }
14820   return ret;
14821 }
14822
14823 char *
14824 StrSavePtr(s, savePtr)
14825      char *s, **savePtr;
14826 {
14827     if (*savePtr) {
14828         free(*savePtr);
14829     }
14830     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14831       safeStrCpy(*savePtr, s, strlen(s)+1);
14832     }
14833     return(*savePtr);
14834 }
14835
14836 char *
14837 PGNDate()
14838 {
14839     time_t clock;
14840     struct tm *tm;
14841     char buf[MSG_SIZ];
14842
14843     clock = time((time_t *)NULL);
14844     tm = localtime(&clock);
14845     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14846             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14847     return StrSave(buf);
14848 }
14849
14850
14851 char *
14852 PositionToFEN(move, overrideCastling)
14853      int move;
14854      char *overrideCastling;
14855 {
14856     int i, j, fromX, fromY, toX, toY;
14857     int whiteToPlay;
14858     char buf[128];
14859     char *p, *q;
14860     int emptycount;
14861     ChessSquare piece;
14862
14863     whiteToPlay = (gameMode == EditPosition) ?
14864       !blackPlaysFirst : (move % 2 == 0);
14865     p = buf;
14866
14867     /* Piece placement data */
14868     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14869         emptycount = 0;
14870         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14871             if (boards[move][i][j] == EmptySquare) {
14872                 emptycount++;
14873             } else { ChessSquare piece = boards[move][i][j];
14874                 if (emptycount > 0) {
14875                     if(emptycount<10) /* [HGM] can be >= 10 */
14876                         *p++ = '0' + emptycount;
14877                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14878                     emptycount = 0;
14879                 }
14880                 if(PieceToChar(piece) == '+') {
14881                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14882                     *p++ = '+';
14883                     piece = (ChessSquare)(DEMOTED piece);
14884                 }
14885                 *p++ = PieceToChar(piece);
14886                 if(p[-1] == '~') {
14887                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14888                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14889                     *p++ = '~';
14890                 }
14891             }
14892         }
14893         if (emptycount > 0) {
14894             if(emptycount<10) /* [HGM] can be >= 10 */
14895                 *p++ = '0' + emptycount;
14896             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14897             emptycount = 0;
14898         }
14899         *p++ = '/';
14900     }
14901     *(p - 1) = ' ';
14902
14903     /* [HGM] print Crazyhouse or Shogi holdings */
14904     if( gameInfo.holdingsWidth ) {
14905         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14906         q = p;
14907         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14908             piece = boards[move][i][BOARD_WIDTH-1];
14909             if( piece != EmptySquare )
14910               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14911                   *p++ = PieceToChar(piece);
14912         }
14913         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14914             piece = boards[move][BOARD_HEIGHT-i-1][0];
14915             if( piece != EmptySquare )
14916               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14917                   *p++ = PieceToChar(piece);
14918         }
14919
14920         if( q == p ) *p++ = '-';
14921         *p++ = ']';
14922         *p++ = ' ';
14923     }
14924
14925     /* Active color */
14926     *p++ = whiteToPlay ? 'w' : 'b';
14927     *p++ = ' ';
14928
14929   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14930     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14931   } else {
14932   if(nrCastlingRights) {
14933      q = p;
14934      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14935        /* [HGM] write directly from rights */
14936            if(boards[move][CASTLING][2] != NoRights &&
14937               boards[move][CASTLING][0] != NoRights   )
14938                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14939            if(boards[move][CASTLING][2] != NoRights &&
14940               boards[move][CASTLING][1] != NoRights   )
14941                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14942            if(boards[move][CASTLING][5] != NoRights &&
14943               boards[move][CASTLING][3] != NoRights   )
14944                 *p++ = boards[move][CASTLING][3] + AAA;
14945            if(boards[move][CASTLING][5] != NoRights &&
14946               boards[move][CASTLING][4] != NoRights   )
14947                 *p++ = boards[move][CASTLING][4] + AAA;
14948      } else {
14949
14950         /* [HGM] write true castling rights */
14951         if( nrCastlingRights == 6 ) {
14952             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14953                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14954             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14955                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14956             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14957                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14958             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14959                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14960         }
14961      }
14962      if (q == p) *p++ = '-'; /* No castling rights */
14963      *p++ = ' ';
14964   }
14965
14966   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14967      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14968     /* En passant target square */
14969     if (move > backwardMostMove) {
14970         fromX = moveList[move - 1][0] - AAA;
14971         fromY = moveList[move - 1][1] - ONE;
14972         toX = moveList[move - 1][2] - AAA;
14973         toY = moveList[move - 1][3] - ONE;
14974         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14975             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14976             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14977             fromX == toX) {
14978             /* 2-square pawn move just happened */
14979             *p++ = toX + AAA;
14980             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14981         } else {
14982             *p++ = '-';
14983         }
14984     } else if(move == backwardMostMove) {
14985         // [HGM] perhaps we should always do it like this, and forget the above?
14986         if((signed char)boards[move][EP_STATUS] >= 0) {
14987             *p++ = boards[move][EP_STATUS] + AAA;
14988             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14989         } else {
14990             *p++ = '-';
14991         }
14992     } else {
14993         *p++ = '-';
14994     }
14995     *p++ = ' ';
14996   }
14997   }
14998
14999     /* [HGM] find reversible plies */
15000     {   int i = 0, j=move;
15001
15002         if (appData.debugMode) { int k;
15003             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15004             for(k=backwardMostMove; k<=forwardMostMove; k++)
15005                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15006
15007         }
15008
15009         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15010         if( j == backwardMostMove ) i += initialRulePlies;
15011         sprintf(p, "%d ", i);
15012         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15013     }
15014     /* Fullmove number */
15015     sprintf(p, "%d", (move / 2) + 1);
15016
15017     return StrSave(buf);
15018 }
15019
15020 Boolean
15021 ParseFEN(board, blackPlaysFirst, fen)
15022     Board board;
15023      int *blackPlaysFirst;
15024      char *fen;
15025 {
15026     int i, j;
15027     char *p, c;
15028     int emptycount;
15029     ChessSquare piece;
15030
15031     p = fen;
15032
15033     /* [HGM] by default clear Crazyhouse holdings, if present */
15034     if(gameInfo.holdingsWidth) {
15035        for(i=0; i<BOARD_HEIGHT; i++) {
15036            board[i][0]             = EmptySquare; /* black holdings */
15037            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15038            board[i][1]             = (ChessSquare) 0; /* black counts */
15039            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15040        }
15041     }
15042
15043     /* Piece placement data */
15044     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15045         j = 0;
15046         for (;;) {
15047             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15048                 if (*p == '/') p++;
15049                 emptycount = gameInfo.boardWidth - j;
15050                 while (emptycount--)
15051                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15052                 break;
15053 #if(BOARD_FILES >= 10)
15054             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15055                 p++; emptycount=10;
15056                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15057                 while (emptycount--)
15058                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15059 #endif
15060             } else if (isdigit(*p)) {
15061                 emptycount = *p++ - '0';
15062                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15063                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15064                 while (emptycount--)
15065                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15066             } else if (*p == '+' || isalpha(*p)) {
15067                 if (j >= gameInfo.boardWidth) return FALSE;
15068                 if(*p=='+') {
15069                     piece = CharToPiece(*++p);
15070                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15071                     piece = (ChessSquare) (PROMOTED piece ); p++;
15072                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15073                 } else piece = CharToPiece(*p++);
15074
15075                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15076                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15077                     piece = (ChessSquare) (PROMOTED piece);
15078                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15079                     p++;
15080                 }
15081                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15082             } else {
15083                 return FALSE;
15084             }
15085         }
15086     }
15087     while (*p == '/' || *p == ' ') p++;
15088
15089     /* [HGM] look for Crazyhouse holdings here */
15090     while(*p==' ') p++;
15091     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15092         if(*p == '[') p++;
15093         if(*p == '-' ) p++; /* empty holdings */ else {
15094             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15095             /* if we would allow FEN reading to set board size, we would   */
15096             /* have to add holdings and shift the board read so far here   */
15097             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15098                 p++;
15099                 if((int) piece >= (int) BlackPawn ) {
15100                     i = (int)piece - (int)BlackPawn;
15101                     i = PieceToNumber((ChessSquare)i);
15102                     if( i >= gameInfo.holdingsSize ) return FALSE;
15103                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15104                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15105                 } else {
15106                     i = (int)piece - (int)WhitePawn;
15107                     i = PieceToNumber((ChessSquare)i);
15108                     if( i >= gameInfo.holdingsSize ) return FALSE;
15109                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15110                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15111                 }
15112             }
15113         }
15114         if(*p == ']') p++;
15115     }
15116
15117     while(*p == ' ') p++;
15118
15119     /* Active color */
15120     c = *p++;
15121     if(appData.colorNickNames) {
15122       if( c == appData.colorNickNames[0] ) c = 'w'; else
15123       if( c == appData.colorNickNames[1] ) c = 'b';
15124     }
15125     switch (c) {
15126       case 'w':
15127         *blackPlaysFirst = FALSE;
15128         break;
15129       case 'b':
15130         *blackPlaysFirst = TRUE;
15131         break;
15132       default:
15133         return FALSE;
15134     }
15135
15136     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15137     /* return the extra info in global variiables             */
15138
15139     /* set defaults in case FEN is incomplete */
15140     board[EP_STATUS] = EP_UNKNOWN;
15141     for(i=0; i<nrCastlingRights; i++ ) {
15142         board[CASTLING][i] =
15143             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15144     }   /* assume possible unless obviously impossible */
15145     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15146     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15147     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15148                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15149     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15150     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15151     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15152                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15153     FENrulePlies = 0;
15154
15155     while(*p==' ') p++;
15156     if(nrCastlingRights) {
15157       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15158           /* castling indicator present, so default becomes no castlings */
15159           for(i=0; i<nrCastlingRights; i++ ) {
15160                  board[CASTLING][i] = NoRights;
15161           }
15162       }
15163       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15164              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15165              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15166              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15167         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15168
15169         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15170             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15171             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15172         }
15173         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15174             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15175         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15176                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15177         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15178                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15179         switch(c) {
15180           case'K':
15181               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15182               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15183               board[CASTLING][2] = whiteKingFile;
15184               break;
15185           case'Q':
15186               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15187               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15188               board[CASTLING][2] = whiteKingFile;
15189               break;
15190           case'k':
15191               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15192               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15193               board[CASTLING][5] = blackKingFile;
15194               break;
15195           case'q':
15196               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15197               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15198               board[CASTLING][5] = blackKingFile;
15199           case '-':
15200               break;
15201           default: /* FRC castlings */
15202               if(c >= 'a') { /* black rights */
15203                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15204                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15205                   if(i == BOARD_RGHT) break;
15206                   board[CASTLING][5] = i;
15207                   c -= AAA;
15208                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15209                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15210                   if(c > i)
15211                       board[CASTLING][3] = c;
15212                   else
15213                       board[CASTLING][4] = c;
15214               } else { /* white rights */
15215                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15216                     if(board[0][i] == WhiteKing) break;
15217                   if(i == BOARD_RGHT) break;
15218                   board[CASTLING][2] = i;
15219                   c -= AAA - 'a' + 'A';
15220                   if(board[0][c] >= WhiteKing) break;
15221                   if(c > i)
15222                       board[CASTLING][0] = c;
15223                   else
15224                       board[CASTLING][1] = c;
15225               }
15226         }
15227       }
15228       for(i=0; i<nrCastlingRights; i++)
15229         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15230     if (appData.debugMode) {
15231         fprintf(debugFP, "FEN castling rights:");
15232         for(i=0; i<nrCastlingRights; i++)
15233         fprintf(debugFP, " %d", board[CASTLING][i]);
15234         fprintf(debugFP, "\n");
15235     }
15236
15237       while(*p==' ') p++;
15238     }
15239
15240     /* read e.p. field in games that know e.p. capture */
15241     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15242        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15243       if(*p=='-') {
15244         p++; board[EP_STATUS] = EP_NONE;
15245       } else {
15246          char c = *p++ - AAA;
15247
15248          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15249          if(*p >= '0' && *p <='9') p++;
15250          board[EP_STATUS] = c;
15251       }
15252     }
15253
15254
15255     if(sscanf(p, "%d", &i) == 1) {
15256         FENrulePlies = i; /* 50-move ply counter */
15257         /* (The move number is still ignored)    */
15258     }
15259
15260     return TRUE;
15261 }
15262
15263 void
15264 EditPositionPasteFEN(char *fen)
15265 {
15266   if (fen != NULL) {
15267     Board initial_position;
15268
15269     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15270       DisplayError(_("Bad FEN position in clipboard"), 0);
15271       return ;
15272     } else {
15273       int savedBlackPlaysFirst = blackPlaysFirst;
15274       EditPositionEvent();
15275       blackPlaysFirst = savedBlackPlaysFirst;
15276       CopyBoard(boards[0], initial_position);
15277       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15278       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15279       DisplayBothClocks();
15280       DrawPosition(FALSE, boards[currentMove]);
15281     }
15282   }
15283 }
15284
15285 static char cseq[12] = "\\   ";
15286
15287 Boolean set_cont_sequence(char *new_seq)
15288 {
15289     int len;
15290     Boolean ret;
15291
15292     // handle bad attempts to set the sequence
15293         if (!new_seq)
15294                 return 0; // acceptable error - no debug
15295
15296     len = strlen(new_seq);
15297     ret = (len > 0) && (len < sizeof(cseq));
15298     if (ret)
15299       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15300     else if (appData.debugMode)
15301       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15302     return ret;
15303 }
15304
15305 /*
15306     reformat a source message so words don't cross the width boundary.  internal
15307     newlines are not removed.  returns the wrapped size (no null character unless
15308     included in source message).  If dest is NULL, only calculate the size required
15309     for the dest buffer.  lp argument indicats line position upon entry, and it's
15310     passed back upon exit.
15311 */
15312 int wrap(char *dest, char *src, int count, int width, int *lp)
15313 {
15314     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15315
15316     cseq_len = strlen(cseq);
15317     old_line = line = *lp;
15318     ansi = len = clen = 0;
15319
15320     for (i=0; i < count; i++)
15321     {
15322         if (src[i] == '\033')
15323             ansi = 1;
15324
15325         // if we hit the width, back up
15326         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15327         {
15328             // store i & len in case the word is too long
15329             old_i = i, old_len = len;
15330
15331             // find the end of the last word
15332             while (i && src[i] != ' ' && src[i] != '\n')
15333             {
15334                 i--;
15335                 len--;
15336             }
15337
15338             // word too long?  restore i & len before splitting it
15339             if ((old_i-i+clen) >= width)
15340             {
15341                 i = old_i;
15342                 len = old_len;
15343             }
15344
15345             // extra space?
15346             if (i && src[i-1] == ' ')
15347                 len--;
15348
15349             if (src[i] != ' ' && src[i] != '\n')
15350             {
15351                 i--;
15352                 if (len)
15353                     len--;
15354             }
15355
15356             // now append the newline and continuation sequence
15357             if (dest)
15358                 dest[len] = '\n';
15359             len++;
15360             if (dest)
15361                 strncpy(dest+len, cseq, cseq_len);
15362             len += cseq_len;
15363             line = cseq_len;
15364             clen = cseq_len;
15365             continue;
15366         }
15367
15368         if (dest)
15369             dest[len] = src[i];
15370         len++;
15371         if (!ansi)
15372             line++;
15373         if (src[i] == '\n')
15374             line = 0;
15375         if (src[i] == 'm')
15376             ansi = 0;
15377     }
15378     if (dest && appData.debugMode)
15379     {
15380         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15381             count, width, line, len, *lp);
15382         show_bytes(debugFP, src, count);
15383         fprintf(debugFP, "\ndest: ");
15384         show_bytes(debugFP, dest, len);
15385         fprintf(debugFP, "\n");
15386     }
15387     *lp = dest ? line : old_line;
15388
15389     return len;
15390 }
15391
15392 // [HGM] vari: routines for shelving variations
15393
15394 void
15395 PushTail(int firstMove, int lastMove)
15396 {
15397         int i, j, nrMoves = lastMove - firstMove;
15398
15399         if(appData.icsActive) { // only in local mode
15400                 forwardMostMove = currentMove; // mimic old ICS behavior
15401                 return;
15402         }
15403         if(storedGames >= MAX_VARIATIONS-1) return;
15404
15405         // push current tail of game on stack
15406         savedResult[storedGames] = gameInfo.result;
15407         savedDetails[storedGames] = gameInfo.resultDetails;
15408         gameInfo.resultDetails = NULL;
15409         savedFirst[storedGames] = firstMove;
15410         savedLast [storedGames] = lastMove;
15411         savedFramePtr[storedGames] = framePtr;
15412         framePtr -= nrMoves; // reserve space for the boards
15413         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15414             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15415             for(j=0; j<MOVE_LEN; j++)
15416                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15417             for(j=0; j<2*MOVE_LEN; j++)
15418                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15419             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15420             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15421             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15422             pvInfoList[firstMove+i-1].depth = 0;
15423             commentList[framePtr+i] = commentList[firstMove+i];
15424             commentList[firstMove+i] = NULL;
15425         }
15426
15427         storedGames++;
15428         forwardMostMove = firstMove; // truncate game so we can start variation
15429         if(storedGames == 1) GreyRevert(FALSE);
15430 }
15431
15432 Boolean
15433 PopTail(Boolean annotate)
15434 {
15435         int i, j, nrMoves;
15436         char buf[8000], moveBuf[20];
15437
15438         if(appData.icsActive) return FALSE; // only in local mode
15439         if(!storedGames) return FALSE; // sanity
15440         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15441
15442         storedGames--;
15443         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15444         nrMoves = savedLast[storedGames] - currentMove;
15445         if(annotate) {
15446                 int cnt = 10;
15447                 if(!WhiteOnMove(currentMove))
15448                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15449                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15450                 for(i=currentMove; i<forwardMostMove; i++) {
15451                         if(WhiteOnMove(i))
15452                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15453                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15454                         strcat(buf, moveBuf);
15455                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15456                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15457                 }
15458                 strcat(buf, ")");
15459         }
15460         for(i=1; i<=nrMoves; i++) { // copy last variation back
15461             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15462             for(j=0; j<MOVE_LEN; j++)
15463                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15464             for(j=0; j<2*MOVE_LEN; j++)
15465                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15466             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15467             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15468             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15469             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15470             commentList[currentMove+i] = commentList[framePtr+i];
15471             commentList[framePtr+i] = NULL;
15472         }
15473         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15474         framePtr = savedFramePtr[storedGames];
15475         gameInfo.result = savedResult[storedGames];
15476         if(gameInfo.resultDetails != NULL) {
15477             free(gameInfo.resultDetails);
15478       }
15479         gameInfo.resultDetails = savedDetails[storedGames];
15480         forwardMostMove = currentMove + nrMoves;
15481         if(storedGames == 0) GreyRevert(TRUE);
15482         return TRUE;
15483 }
15484
15485 void
15486 CleanupTail()
15487 {       // remove all shelved variations
15488         int i;
15489         for(i=0; i<storedGames; i++) {
15490             if(savedDetails[i])
15491                 free(savedDetails[i]);
15492             savedDetails[i] = NULL;
15493         }
15494         for(i=framePtr; i<MAX_MOVES; i++) {
15495                 if(commentList[i]) free(commentList[i]);
15496                 commentList[i] = NULL;
15497         }
15498         framePtr = MAX_MOVES-1;
15499         storedGames = 0;
15500 }
15501
15502 void
15503 LoadVariation(int index, char *text)
15504 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15505         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15506         int level = 0, move;
15507
15508         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15509         // first find outermost bracketing variation
15510         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15511             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15512                 if(*p == '{') wait = '}'; else
15513                 if(*p == '[') wait = ']'; else
15514                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15515                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15516             }
15517             if(*p == wait) wait = NULLCHAR; // closing ]} found
15518             p++;
15519         }
15520         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15521         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15522         end[1] = NULLCHAR; // clip off comment beyond variation
15523         ToNrEvent(currentMove-1);
15524         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15525         // kludge: use ParsePV() to append variation to game
15526         move = currentMove;
15527         ParsePV(start, TRUE);
15528         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15529         ClearPremoveHighlights();
15530         CommentPopDown();
15531         ToNrEvent(currentMove+1);
15532 }
15533