26b7d805557125b6ec29836083bef4b953c7216d
[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 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
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 { // [HGM] made safe
315   int i;
316   assert( dst != NULL );
317   assert( src != NULL );
318   assert( count > 0 );
319
320   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
321   if(  i == count && dst[count-1] != NULLCHAR)
322     {
323       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324       if(appData.debugMode)
325       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
326     }
327
328   return dst;
329 }
330
331 /* Some compiler can't cast u64 to double
332  * This function do the job for us:
333
334  * We use the highest bit for cast, this only
335  * works if the highest bit is not
336  * in use (This should not happen)
337  *
338  * We used this for all compiler
339  */
340 double
341 u64ToDouble(u64 value)
342 {
343   double r;
344   u64 tmp = value & u64Const(0x7fffffffffffffff);
345   r = (double)(s64)tmp;
346   if (value & u64Const(0x8000000000000000))
347        r +=  9.2233720368547758080e18; /* 2^63 */
348  return r;
349 }
350
351 /* Fake up flags for now, as we aren't keeping track of castling
352    availability yet. [HGM] Change of logic: the flag now only
353    indicates the type of castlings allowed by the rule of the game.
354    The actual rights themselves are maintained in the array
355    castlingRights, as part of the game history, and are not probed
356    by this function.
357  */
358 int
359 PosFlags(index)
360 {
361   int flags = F_ALL_CASTLE_OK;
362   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
363   switch (gameInfo.variant) {
364   case VariantSuicide:
365     flags &= ~F_ALL_CASTLE_OK;
366   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
367     flags |= F_IGNORE_CHECK;
368   case VariantLosers:
369     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
370     break;
371   case VariantAtomic:
372     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
373     break;
374   case VariantKriegspiel:
375     flags |= F_KRIEGSPIEL_CAPTURE;
376     break;
377   case VariantCapaRandom:
378   case VariantFischeRandom:
379     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
380   case VariantNoCastle:
381   case VariantShatranj:
382   case VariantCourier:
383   case VariantMakruk:
384     flags &= ~F_ALL_CASTLE_OK;
385     break;
386   default:
387     break;
388   }
389   return flags;
390 }
391
392 FILE *gameFileFP, *debugFP;
393
394 /*
395     [AS] Note: sometimes, the sscanf() function is used to parse the input
396     into a fixed-size buffer. Because of this, we must be prepared to
397     receive strings as long as the size of the input buffer, which is currently
398     set to 4K for Windows and 8K for the rest.
399     So, we must either allocate sufficiently large buffers here, or
400     reduce the size of the input buffer in the input reading part.
401 */
402
403 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
404 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
405 char thinkOutput1[MSG_SIZ*10];
406
407 ChessProgramState first, second;
408
409 /* premove variables */
410 int premoveToX = 0;
411 int premoveToY = 0;
412 int premoveFromX = 0;
413 int premoveFromY = 0;
414 int premovePromoChar = 0;
415 int gotPremove = 0;
416 Boolean alarmSounded;
417 /* end premove variables */
418
419 char *ics_prefix = "$";
420 int ics_type = ICS_GENERIC;
421
422 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
423 int pauseExamForwardMostMove = 0;
424 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
425 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
426 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
427 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
428 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
429 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
430 int whiteFlag = FALSE, blackFlag = FALSE;
431 int userOfferedDraw = FALSE;
432 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
433 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
434 int cmailMoveType[CMAIL_MAX_GAMES];
435 long ics_clock_paused = 0;
436 ProcRef icsPR = NoProc, cmailPR = NoProc;
437 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
438 GameMode gameMode = BeginningOfGame;
439 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
440 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
441 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
442 int hiddenThinkOutputState = 0; /* [AS] */
443 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
444 int adjudicateLossPlies = 6;
445 char white_holding[64], black_holding[64];
446 TimeMark lastNodeCountTime;
447 long lastNodeCount=0;
448 int shiftKey; // [HGM] set by mouse handler
449
450 int have_sent_ICS_logon = 0;
451 int movesPerSession;
452 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
453 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
454 long timeControl_2; /* [AS] Allow separate time controls */
455 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
456 long timeRemaining[2][MAX_MOVES];
457 int matchGame = 0;
458 TimeMark programStartTime;
459 char ics_handle[MSG_SIZ];
460 int have_set_title = 0;
461
462 /* animateTraining preserves the state of appData.animate
463  * when Training mode is activated. This allows the
464  * response to be animated when appData.animate == TRUE and
465  * appData.animateDragging == TRUE.
466  */
467 Boolean animateTraining;
468
469 GameInfo gameInfo;
470
471 AppData appData;
472
473 Board boards[MAX_MOVES];
474 /* [HGM] Following 7 needed for accurate legality tests: */
475 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
476 signed char  initialRights[BOARD_FILES];
477 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
478 int   initialRulePlies, FENrulePlies;
479 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
480 int loadFlag = 0;
481 int shuffleOpenings;
482 int mute; // mute all sounds
483
484 // [HGM] vari: next 12 to save and restore variations
485 #define MAX_VARIATIONS 10
486 int framePtr = MAX_MOVES-1; // points to free stack entry
487 int storedGames = 0;
488 int savedFirst[MAX_VARIATIONS];
489 int savedLast[MAX_VARIATIONS];
490 int savedFramePtr[MAX_VARIATIONS];
491 char *savedDetails[MAX_VARIATIONS];
492 ChessMove savedResult[MAX_VARIATIONS];
493
494 void PushTail P((int firstMove, int lastMove));
495 Boolean PopTail P((Boolean annotate));
496 void CleanupTail P((void));
497
498 ChessSquare  FIDEArray[2][BOARD_FILES] = {
499     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
500         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
502         BlackKing, BlackBishop, BlackKnight, BlackRook }
503 };
504
505 ChessSquare twoKingsArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackKing, BlackKnight, BlackRook }
510 };
511
512 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
514         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
515     { BlackRook, BlackMan, BlackBishop, BlackQueen,
516         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
517 };
518
519 ChessSquare SpartanArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
523         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
524 };
525
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
530         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
531 };
532
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackMan, BlackFerz,
544         BlackKing, BlackMan, BlackKnight, BlackRook }
545 };
546
547
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
554 };
555
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
561 };
562
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
568 };
569
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
575 };
576
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
582 };
583
584 #ifdef GOTHIC
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
590 };
591 #else // !GOTHIC
592 #define GothicArray CapablancaArray
593 #endif // !GOTHIC
594
595 #ifdef FALCON
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
601 };
602 #else // !FALCON
603 #define FalconArray CapablancaArray
604 #endif // !FALCON
605
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
612
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
619 };
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
623
624
625 Board initialPosition;
626
627
628 /* Convert str to a rating. Checks for special cases of "----",
629
630    "++++", etc. Also strips ()'s */
631 int
632 string_to_rating(str)
633   char *str;
634 {
635   while(*str && !isdigit(*str)) ++str;
636   if (!*str)
637     return 0;   /* One of the special "no rating" cases */
638   else
639     return atoi(str);
640 }
641
642 void
643 ClearProgramStats()
644 {
645     /* Init programStats */
646     programStats.movelist[0] = 0;
647     programStats.depth = 0;
648     programStats.nr_moves = 0;
649     programStats.moves_left = 0;
650     programStats.nodes = 0;
651     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
652     programStats.score = 0;
653     programStats.got_only_move = 0;
654     programStats.got_fail = 0;
655     programStats.line_is_book = 0;
656 }
657
658 void
659 InitBackEnd1()
660 {
661     int matched, min, sec;
662
663     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
665
666     GetTimeMark(&programStartTime);
667     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
668
669     ClearProgramStats();
670     programStats.ok_to_send = 1;
671     programStats.seen_stat = 0;
672
673     /*
674      * Initialize game list
675      */
676     ListNew(&gameList);
677
678
679     /*
680      * Internet chess server status
681      */
682     if (appData.icsActive) {
683         appData.matchMode = FALSE;
684         appData.matchGames = 0;
685 #if ZIPPY
686         appData.noChessProgram = !appData.zippyPlay;
687 #else
688         appData.zippyPlay = FALSE;
689         appData.zippyTalk = FALSE;
690         appData.noChessProgram = TRUE;
691 #endif
692         if (*appData.icsHelper != NULLCHAR) {
693             appData.useTelnet = TRUE;
694             appData.telnetProgram = appData.icsHelper;
695         }
696     } else {
697         appData.zippyTalk = appData.zippyPlay = FALSE;
698     }
699
700     /* [AS] Initialize pv info list [HGM] and game state */
701     {
702         int i, j;
703
704         for( i=0; i<=framePtr; i++ ) {
705             pvInfoList[i].depth = -1;
706             boards[i][EP_STATUS] = EP_NONE;
707             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708         }
709     }
710
711     /*
712      * Parse timeControl resource
713      */
714     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715                           appData.movesPerSession)) {
716         char buf[MSG_SIZ];
717         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718         DisplayFatalError(buf, 0, 2);
719     }
720
721     /*
722      * Parse searchTime resource
723      */
724     if (*appData.searchTime != NULLCHAR) {
725         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
726         if (matched == 1) {
727             searchTime = min * 60;
728         } else if (matched == 2) {
729             searchTime = min * 60 + sec;
730         } else {
731             char buf[MSG_SIZ];
732             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733             DisplayFatalError(buf, 0, 2);
734         }
735     }
736
737     /* [AS] Adjudication threshold */
738     adjudicateLossThreshold = appData.adjudicateLossThreshold;
739
740     first.which = _("first");
741     second.which = _("second");
742     first.maybeThinking = second.maybeThinking = FALSE;
743     first.pr = second.pr = NoProc;
744     first.isr = second.isr = NULL;
745     first.sendTime = second.sendTime = 2;
746     first.sendDrawOffers = 1;
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754     first.program = appData.firstChessProgram;
755     second.program = appData.secondChessProgram;
756     first.host = appData.firstHost;
757     second.host = appData.secondHost;
758     first.dir = appData.firstDirectory;
759     second.dir = appData.secondDirectory;
760     first.other = &second;
761     second.other = &first;
762     first.initString = appData.initString;
763     second.initString = appData.secondInitString;
764     first.computerString = appData.firstComputerString;
765     second.computerString = appData.secondComputerString;
766     first.useSigint = second.useSigint = TRUE;
767     first.useSigterm = second.useSigterm = TRUE;
768     first.reuse = appData.reuseFirst;
769     second.reuse = appData.reuseSecond;
770     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
771     second.nps = appData.secondNPS;
772     first.useSetboard = second.useSetboard = FALSE;
773     first.useSAN = second.useSAN = FALSE;
774     first.usePing = second.usePing = FALSE;
775     first.lastPing = second.lastPing = 0;
776     first.lastPong = second.lastPong = 0;
777     first.usePlayother = second.usePlayother = FALSE;
778     first.useColors = second.useColors = TRUE;
779     first.useUsermove = second.useUsermove = FALSE;
780     first.sendICS = second.sendICS = FALSE;
781     first.sendName = second.sendName = appData.icsActive;
782     first.sdKludge = second.sdKludge = FALSE;
783     first.stKludge = second.stKludge = FALSE;
784     TidyProgramName(first.program, first.host, first.tidy);
785     TidyProgramName(second.program, second.host, second.tidy);
786     first.matchWins = second.matchWins = 0;
787     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789     first.analysisSupport = second.analysisSupport = 2; /* detect */
790     first.analyzing = second.analyzing = FALSE;
791     first.initDone = second.initDone = FALSE;
792
793     /* New features added by Tord: */
794     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     first.fenOverride  = appData.fenOverride1;
798     second.fenOverride = appData.fenOverride2;
799
800     /* [HGM] time odds: set factor for each machine */
801     first.timeOdds  = appData.firstTimeOdds;
802     second.timeOdds = appData.secondTimeOdds;
803     { float norm = 1;
804         if(appData.timeOddsMode) {
805             norm = first.timeOdds;
806             if(norm > second.timeOdds) norm = second.timeOdds;
807         }
808         first.timeOdds /= norm;
809         second.timeOdds /= norm;
810     }
811
812     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813     first.accumulateTC = appData.firstAccumulateTC;
814     second.accumulateTC = appData.secondAccumulateTC;
815     first.maxNrOfSessions = second.maxNrOfSessions = 1;
816
817     /* [HGM] debug */
818     first.debug = second.debug = FALSE;
819     first.supportsNPS = second.supportsNPS = UNKNOWN;
820
821     /* [HGM] options */
822     first.optionSettings  = appData.firstOptions;
823     second.optionSettings = appData.secondOptions;
824
825     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827     first.isUCI = appData.firstIsUCI; /* [AS] */
828     second.isUCI = appData.secondIsUCI; /* [AS] */
829     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
831
832     if (appData.firstProtocolVersion > PROTOVER
833         || appData.firstProtocolVersion < 1)
834       {
835         char buf[MSG_SIZ];
836         int len;
837
838         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839                        appData.firstProtocolVersion);
840         if( (len > MSG_SIZ) && appData.debugMode )
841           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842
843         DisplayFatalError(buf, 0, 2);
844       }
845     else
846       {
847         first.protocolVersion = appData.firstProtocolVersion;
848       }
849
850     if (appData.secondProtocolVersion > PROTOVER
851         || appData.secondProtocolVersion < 1)
852       {
853         char buf[MSG_SIZ];
854         int len;
855
856         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857                        appData.secondProtocolVersion);
858         if( (len > MSG_SIZ) && appData.debugMode )
859           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
860
861         DisplayFatalError(buf, 0, 2);
862       }
863     else
864       {
865         second.protocolVersion = appData.secondProtocolVersion;
866       }
867
868     if (appData.icsActive) {
869         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
870 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872         appData.clockMode = FALSE;
873         first.sendTime = second.sendTime = 0;
874     }
875
876 #if ZIPPY
877     /* Override some settings from environment variables, for backward
878        compatibility.  Unfortunately it's not feasible to have the env
879        vars just set defaults, at least in xboard.  Ugh.
880     */
881     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882       ZippyInit();
883     }
884 #endif
885
886     if (appData.noChessProgram) {
887         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888         sprintf(programVersion, "%s", PACKAGE_STRING);
889     } else {
890       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
893     }
894
895     if (!appData.icsActive) {
896       char buf[MSG_SIZ];
897       int len;
898
899       /* Check for variants that are supported only in ICS mode,
900          or not at all.  Some that are accepted here nevertheless
901          have bugs; see comments below.
902       */
903       VariantClass variant = StringToVariant(appData.variant);
904       switch (variant) {
905       case VariantBughouse:     /* need four players and two boards */
906       case VariantKriegspiel:   /* need to hide pieces and move details */
907         /* case VariantFischeRandom: (Fabien: moved below) */
908         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909         if( (len > MSG_SIZ) && appData.debugMode )
910           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
911
912         DisplayFatalError(buf, 0, 2);
913         return;
914
915       case VariantUnknown:
916       case VariantLoadable:
917       case Variant29:
918       case Variant30:
919       case Variant31:
920       case Variant32:
921       case Variant33:
922       case Variant34:
923       case Variant35:
924       case Variant36:
925       default:
926         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927         if( (len > MSG_SIZ) && appData.debugMode )
928           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
929
930         DisplayFatalError(buf, 0, 2);
931         return;
932
933       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
934       case VariantFairy:      /* [HGM] TestLegality definitely off! */
935       case VariantGothic:     /* [HGM] should work */
936       case VariantCapablanca: /* [HGM] should work */
937       case VariantCourier:    /* [HGM] initial forced moves not implemented */
938       case VariantShogi:      /* [HGM] could still mate with pawn drop */
939       case VariantKnightmate: /* [HGM] should work */
940       case VariantCylinder:   /* [HGM] untested */
941       case VariantFalcon:     /* [HGM] untested */
942       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943                                  offboard interposition not understood */
944       case VariantNormal:     /* definitely works! */
945       case VariantWildCastle: /* pieces not automatically shuffled */
946       case VariantNoCastle:   /* pieces not automatically shuffled */
947       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948       case VariantLosers:     /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantSuicide:    /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantGiveaway:   /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantTwoKings:   /* should work */
955       case VariantAtomic:     /* should work except for win condition */
956       case Variant3Check:     /* should work except for win condition */
957       case VariantShatranj:   /* should work except for all win conditions */
958       case VariantMakruk:     /* should work except for daw countdown */
959       case VariantBerolina:   /* might work if TestLegality is off */
960       case VariantCapaRandom: /* should work */
961       case VariantJanus:      /* should work */
962       case VariantSuper:      /* experimental */
963       case VariantGreat:      /* experimental, requires legality testing to be off */
964       case VariantSChess:     /* S-Chess, should work */
965       case VariantSpartan:    /* should work */
966         break;
967       }
968     }
969
970     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
971     InitEngineUCI( installDir, &second );
972 }
973
974 int NextIntegerFromString( char ** str, long * value )
975 {
976     int result = -1;
977     char * s = *str;
978
979     while( *s == ' ' || *s == '\t' ) {
980         s++;
981     }
982
983     *value = 0;
984
985     if( *s >= '0' && *s <= '9' ) {
986         while( *s >= '0' && *s <= '9' ) {
987             *value = *value * 10 + (*s - '0');
988             s++;
989         }
990
991         result = 0;
992     }
993
994     *str = s;
995
996     return result;
997 }
998
999 int NextTimeControlFromString( char ** str, long * value )
1000 {
1001     long temp;
1002     int result = NextIntegerFromString( str, &temp );
1003
1004     if( result == 0 ) {
1005         *value = temp * 60; /* Minutes */
1006         if( **str == ':' ) {
1007             (*str)++;
1008             result = NextIntegerFromString( str, &temp );
1009             *value += temp; /* Seconds */
1010         }
1011     }
1012
1013     return result;
1014 }
1015
1016 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1017 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1018     int result = -1, type = 0; long temp, temp2;
1019
1020     if(**str != ':') return -1; // old params remain in force!
1021     (*str)++;
1022     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1023     if( NextIntegerFromString( str, &temp ) ) return -1;
1024     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1025
1026     if(**str != '/') {
1027         /* time only: incremental or sudden-death time control */
1028         if(**str == '+') { /* increment follows; read it */
1029             (*str)++;
1030             if(**str == '!') type = *(*str)++; // Bronstein TC
1031             if(result = NextIntegerFromString( str, &temp2)) return -1;
1032             *inc = temp2 * 1000;
1033             if(**str == '.') { // read fraction of increment
1034                 char *start = ++(*str);
1035                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1036                 temp2 *= 1000;
1037                 while(start++ < *str) temp2 /= 10;
1038                 *inc += temp2;
1039             }
1040         } else *inc = 0;
1041         *moves = 0; *tc = temp * 1000; *incType = type;
1042         return 0;
1043     }
1044
1045     (*str)++; /* classical time control */
1046     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1047
1048     if(result == 0) {
1049         *moves = temp;
1050         *tc    = temp2 * 1000;
1051         *inc   = 0;
1052         *incType = type;
1053     }
1054     return result;
1055 }
1056
1057 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1058 {   /* [HGM] get time to add from the multi-session time-control string */
1059     int incType, moves=1; /* kludge to force reading of first session */
1060     long time, increment;
1061     char *s = tcString;
1062
1063     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1064     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1065     do {
1066         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1067         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1068         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1069         if(movenr == -1) return time;    /* last move before new session     */
1070         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1071         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1072         if(!moves) return increment;     /* current session is incremental   */
1073         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1074     } while(movenr >= -1);               /* try again for next session       */
1075
1076     return 0; // no new time quota on this move
1077 }
1078
1079 int
1080 ParseTimeControl(tc, ti, mps)
1081      char *tc;
1082      float ti;
1083      int mps;
1084 {
1085   long tc1;
1086   long tc2;
1087   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1088   int min, sec=0;
1089
1090   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1091   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1092       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1093   if(ti > 0) {
1094
1095     if(mps)
1096       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1097     else 
1098       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1099   } else {
1100     if(mps)
1101       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1102     else 
1103       snprintf(buf, MSG_SIZ, ":%s", mytc);
1104   }
1105   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1106   
1107   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1108     return FALSE;
1109   }
1110
1111   if( *tc == '/' ) {
1112     /* Parse second time control */
1113     tc++;
1114
1115     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1116       return FALSE;
1117     }
1118
1119     if( tc2 == 0 ) {
1120       return FALSE;
1121     }
1122
1123     timeControl_2 = tc2 * 1000;
1124   }
1125   else {
1126     timeControl_2 = 0;
1127   }
1128
1129   if( tc1 == 0 ) {
1130     return FALSE;
1131   }
1132
1133   timeControl = tc1 * 1000;
1134
1135   if (ti >= 0) {
1136     timeIncrement = ti * 1000;  /* convert to ms */
1137     movesPerSession = 0;
1138   } else {
1139     timeIncrement = 0;
1140     movesPerSession = mps;
1141   }
1142   return TRUE;
1143 }
1144
1145 void
1146 InitBackEnd2()
1147 {
1148     if (appData.debugMode) {
1149         fprintf(debugFP, "%s\n", programVersion);
1150     }
1151
1152     set_cont_sequence(appData.wrapContSeq);
1153     if (appData.matchGames > 0) {
1154         appData.matchMode = TRUE;
1155     } else if (appData.matchMode) {
1156         appData.matchGames = 1;
1157     }
1158     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1159         appData.matchGames = appData.sameColorGames;
1160     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1161         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1162         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1163     }
1164     Reset(TRUE, FALSE);
1165     if (appData.noChessProgram || first.protocolVersion == 1) {
1166       InitBackEnd3();
1167     } else {
1168       /* kludge: allow timeout for initial "feature" commands */
1169       FreezeUI();
1170       DisplayMessage("", _("Starting chess program"));
1171       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1172     }
1173 }
1174
1175 void
1176 InitBackEnd3 P((void))
1177 {
1178     GameMode initialMode;
1179     char buf[MSG_SIZ];
1180     int err, len;
1181
1182     InitChessProgram(&first, startedFromSetupPosition);
1183
1184     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1185         free(programVersion);
1186         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1187         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1188     }
1189
1190     if (appData.icsActive) {
1191 #ifdef WIN32
1192         /* [DM] Make a console window if needed [HGM] merged ifs */
1193         ConsoleCreate();
1194 #endif
1195         err = establish();
1196         if (err != 0)
1197           {
1198             if (*appData.icsCommPort != NULLCHAR)
1199               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1200                              appData.icsCommPort);
1201             else
1202               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1203                         appData.icsHost, appData.icsPort);
1204
1205             if( (len > MSG_SIZ) && appData.debugMode )
1206               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1207
1208             DisplayFatalError(buf, err, 1);
1209             return;
1210         }
1211         SetICSMode();
1212         telnetISR =
1213           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1214         fromUserISR =
1215           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1216         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1217             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1218     } else if (appData.noChessProgram) {
1219         SetNCPMode();
1220     } else {
1221         SetGNUMode();
1222     }
1223
1224     if (*appData.cmailGameName != NULLCHAR) {
1225         SetCmailMode();
1226         OpenLoopback(&cmailPR);
1227         cmailISR =
1228           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1229     }
1230
1231     ThawUI();
1232     DisplayMessage("", "");
1233     if (StrCaseCmp(appData.initialMode, "") == 0) {
1234       initialMode = BeginningOfGame;
1235     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1236       initialMode = TwoMachinesPlay;
1237     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1238       initialMode = AnalyzeFile;
1239     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1240       initialMode = AnalyzeMode;
1241     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1242       initialMode = MachinePlaysWhite;
1243     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1244       initialMode = MachinePlaysBlack;
1245     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1246       initialMode = EditGame;
1247     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1248       initialMode = EditPosition;
1249     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1250       initialMode = Training;
1251     } else {
1252       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1253       if( (len > MSG_SIZ) && appData.debugMode )
1254         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1255
1256       DisplayFatalError(buf, 0, 2);
1257       return;
1258     }
1259
1260     if (appData.matchMode) {
1261         /* Set up machine vs. machine match */
1262         if (appData.noChessProgram) {
1263             DisplayFatalError(_("Can't have a match with no chess programs"),
1264                               0, 2);
1265             return;
1266         }
1267         matchMode = TRUE;
1268         matchGame = 1;
1269         if (*appData.loadGameFile != NULLCHAR) {
1270             int index = appData.loadGameIndex; // [HGM] autoinc
1271             if(index<0) lastIndex = index = 1;
1272             if (!LoadGameFromFile(appData.loadGameFile,
1273                                   index,
1274                                   appData.loadGameFile, FALSE)) {
1275                 DisplayFatalError(_("Bad game file"), 0, 1);
1276                 return;
1277             }
1278         } else if (*appData.loadPositionFile != NULLCHAR) {
1279             int index = appData.loadPositionIndex; // [HGM] autoinc
1280             if(index<0) lastIndex = index = 1;
1281             if (!LoadPositionFromFile(appData.loadPositionFile,
1282                                       index,
1283                                       appData.loadPositionFile)) {
1284                 DisplayFatalError(_("Bad position file"), 0, 1);
1285                 return;
1286             }
1287         }
1288         TwoMachinesEvent();
1289     } else if (*appData.cmailGameName != NULLCHAR) {
1290         /* Set up cmail mode */
1291         ReloadCmailMsgEvent(TRUE);
1292     } else {
1293         /* Set up other modes */
1294         if (initialMode == AnalyzeFile) {
1295           if (*appData.loadGameFile == NULLCHAR) {
1296             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1297             return;
1298           }
1299         }
1300         if (*appData.loadGameFile != NULLCHAR) {
1301             (void) LoadGameFromFile(appData.loadGameFile,
1302                                     appData.loadGameIndex,
1303                                     appData.loadGameFile, TRUE);
1304         } else if (*appData.loadPositionFile != NULLCHAR) {
1305             (void) LoadPositionFromFile(appData.loadPositionFile,
1306                                         appData.loadPositionIndex,
1307                                         appData.loadPositionFile);
1308             /* [HGM] try to make self-starting even after FEN load */
1309             /* to allow automatic setup of fairy variants with wtm */
1310             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1311                 gameMode = BeginningOfGame;
1312                 setboardSpoiledMachineBlack = 1;
1313             }
1314             /* [HGM] loadPos: make that every new game uses the setup */
1315             /* from file as long as we do not switch variant          */
1316             if(!blackPlaysFirst) {
1317                 startedFromPositionFile = TRUE;
1318                 CopyBoard(filePosition, boards[0]);
1319             }
1320         }
1321         if (initialMode == AnalyzeMode) {
1322           if (appData.noChessProgram) {
1323             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1324             return;
1325           }
1326           if (appData.icsActive) {
1327             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1328             return;
1329           }
1330           AnalyzeModeEvent();
1331         } else if (initialMode == AnalyzeFile) {
1332           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1333           ShowThinkingEvent();
1334           AnalyzeFileEvent();
1335           AnalysisPeriodicEvent(1);
1336         } else if (initialMode == MachinePlaysWhite) {
1337           if (appData.noChessProgram) {
1338             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1339                               0, 2);
1340             return;
1341           }
1342           if (appData.icsActive) {
1343             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1344                               0, 2);
1345             return;
1346           }
1347           MachineWhiteEvent();
1348         } else if (initialMode == MachinePlaysBlack) {
1349           if (appData.noChessProgram) {
1350             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1351                               0, 2);
1352             return;
1353           }
1354           if (appData.icsActive) {
1355             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1356                               0, 2);
1357             return;
1358           }
1359           MachineBlackEvent();
1360         } else if (initialMode == TwoMachinesPlay) {
1361           if (appData.noChessProgram) {
1362             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1363                               0, 2);
1364             return;
1365           }
1366           if (appData.icsActive) {
1367             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1368                               0, 2);
1369             return;
1370           }
1371           TwoMachinesEvent();
1372         } else if (initialMode == EditGame) {
1373           EditGameEvent();
1374         } else if (initialMode == EditPosition) {
1375           EditPositionEvent();
1376         } else if (initialMode == Training) {
1377           if (*appData.loadGameFile == NULLCHAR) {
1378             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1379             return;
1380           }
1381           TrainingEvent();
1382         }
1383     }
1384 }
1385
1386 /*
1387  * Establish will establish a contact to a remote host.port.
1388  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1389  *  used to talk to the host.
1390  * Returns 0 if okay, error code if not.
1391  */
1392 int
1393 establish()
1394 {
1395     char buf[MSG_SIZ];
1396
1397     if (*appData.icsCommPort != NULLCHAR) {
1398         /* Talk to the host through a serial comm port */
1399         return OpenCommPort(appData.icsCommPort, &icsPR);
1400
1401     } else if (*appData.gateway != NULLCHAR) {
1402         if (*appData.remoteShell == NULLCHAR) {
1403             /* Use the rcmd protocol to run telnet program on a gateway host */
1404             snprintf(buf, sizeof(buf), "%s %s %s",
1405                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1406             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1407
1408         } else {
1409             /* Use the rsh program to run telnet program on a gateway host */
1410             if (*appData.remoteUser == NULLCHAR) {
1411                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1412                         appData.gateway, appData.telnetProgram,
1413                         appData.icsHost, appData.icsPort);
1414             } else {
1415                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1416                         appData.remoteShell, appData.gateway,
1417                         appData.remoteUser, appData.telnetProgram,
1418                         appData.icsHost, appData.icsPort);
1419             }
1420             return StartChildProcess(buf, "", &icsPR);
1421
1422         }
1423     } else if (appData.useTelnet) {
1424         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1425
1426     } else {
1427         /* TCP socket interface differs somewhat between
1428            Unix and NT; handle details in the front end.
1429            */
1430         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1431     }
1432 }
1433
1434 void EscapeExpand(char *p, char *q)
1435 {       // [HGM] initstring: routine to shape up string arguments
1436         while(*p++ = *q++) if(p[-1] == '\\')
1437             switch(*q++) {
1438                 case 'n': p[-1] = '\n'; break;
1439                 case 'r': p[-1] = '\r'; break;
1440                 case 't': p[-1] = '\t'; break;
1441                 case '\\': p[-1] = '\\'; break;
1442                 case 0: *p = 0; return;
1443                 default: p[-1] = q[-1]; break;
1444             }
1445 }
1446
1447 void
1448 show_bytes(fp, buf, count)
1449      FILE *fp;
1450      char *buf;
1451      int count;
1452 {
1453     while (count--) {
1454         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1455             fprintf(fp, "\\%03o", *buf & 0xff);
1456         } else {
1457             putc(*buf, fp);
1458         }
1459         buf++;
1460     }
1461     fflush(fp);
1462 }
1463
1464 /* Returns an errno value */
1465 int
1466 OutputMaybeTelnet(pr, message, count, outError)
1467      ProcRef pr;
1468      char *message;
1469      int count;
1470      int *outError;
1471 {
1472     char buf[8192], *p, *q, *buflim;
1473     int left, newcount, outcount;
1474
1475     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1476         *appData.gateway != NULLCHAR) {
1477         if (appData.debugMode) {
1478             fprintf(debugFP, ">ICS: ");
1479             show_bytes(debugFP, message, count);
1480             fprintf(debugFP, "\n");
1481         }
1482         return OutputToProcess(pr, message, count, outError);
1483     }
1484
1485     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1486     p = message;
1487     q = buf;
1488     left = count;
1489     newcount = 0;
1490     while (left) {
1491         if (q >= buflim) {
1492             if (appData.debugMode) {
1493                 fprintf(debugFP, ">ICS: ");
1494                 show_bytes(debugFP, buf, newcount);
1495                 fprintf(debugFP, "\n");
1496             }
1497             outcount = OutputToProcess(pr, buf, newcount, outError);
1498             if (outcount < newcount) return -1; /* to be sure */
1499             q = buf;
1500             newcount = 0;
1501         }
1502         if (*p == '\n') {
1503             *q++ = '\r';
1504             newcount++;
1505         } else if (((unsigned char) *p) == TN_IAC) {
1506             *q++ = (char) TN_IAC;
1507             newcount ++;
1508         }
1509         *q++ = *p++;
1510         newcount++;
1511         left--;
1512     }
1513     if (appData.debugMode) {
1514         fprintf(debugFP, ">ICS: ");
1515         show_bytes(debugFP, buf, newcount);
1516         fprintf(debugFP, "\n");
1517     }
1518     outcount = OutputToProcess(pr, buf, newcount, outError);
1519     if (outcount < newcount) return -1; /* to be sure */
1520     return count;
1521 }
1522
1523 void
1524 read_from_player(isr, closure, message, count, error)
1525      InputSourceRef isr;
1526      VOIDSTAR closure;
1527      char *message;
1528      int count;
1529      int error;
1530 {
1531     int outError, outCount;
1532     static int gotEof = 0;
1533
1534     /* Pass data read from player on to ICS */
1535     if (count > 0) {
1536         gotEof = 0;
1537         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1538         if (outCount < count) {
1539             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1540         }
1541     } else if (count < 0) {
1542         RemoveInputSource(isr);
1543         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1544     } else if (gotEof++ > 0) {
1545         RemoveInputSource(isr);
1546         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1547     }
1548 }
1549
1550 void
1551 KeepAlive()
1552 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1553     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1554     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1555     SendToICS("date\n");
1556     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1557 }
1558
1559 /* added routine for printf style output to ics */
1560 void ics_printf(char *format, ...)
1561 {
1562     char buffer[MSG_SIZ];
1563     va_list args;
1564
1565     va_start(args, format);
1566     vsnprintf(buffer, sizeof(buffer), format, args);
1567     buffer[sizeof(buffer)-1] = '\0';
1568     SendToICS(buffer);
1569     va_end(args);
1570 }
1571
1572 void
1573 SendToICS(s)
1574      char *s;
1575 {
1576     int count, outCount, outError;
1577
1578     if (icsPR == NULL) return;
1579
1580     count = strlen(s);
1581     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1582     if (outCount < count) {
1583         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1584     }
1585 }
1586
1587 /* This is used for sending logon scripts to the ICS. Sending
1588    without a delay causes problems when using timestamp on ICC
1589    (at least on my machine). */
1590 void
1591 SendToICSDelayed(s,msdelay)
1592      char *s;
1593      long msdelay;
1594 {
1595     int count, outCount, outError;
1596
1597     if (icsPR == NULL) return;
1598
1599     count = strlen(s);
1600     if (appData.debugMode) {
1601         fprintf(debugFP, ">ICS: ");
1602         show_bytes(debugFP, s, count);
1603         fprintf(debugFP, "\n");
1604     }
1605     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1606                                       msdelay);
1607     if (outCount < count) {
1608         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1609     }
1610 }
1611
1612
1613 /* Remove all highlighting escape sequences in s
1614    Also deletes any suffix starting with '('
1615    */
1616 char *
1617 StripHighlightAndTitle(s)
1618      char *s;
1619 {
1620     static char retbuf[MSG_SIZ];
1621     char *p = retbuf;
1622
1623     while (*s != NULLCHAR) {
1624         while (*s == '\033') {
1625             while (*s != NULLCHAR && !isalpha(*s)) s++;
1626             if (*s != NULLCHAR) s++;
1627         }
1628         while (*s != NULLCHAR && *s != '\033') {
1629             if (*s == '(' || *s == '[') {
1630                 *p = NULLCHAR;
1631                 return retbuf;
1632             }
1633             *p++ = *s++;
1634         }
1635     }
1636     *p = NULLCHAR;
1637     return retbuf;
1638 }
1639
1640 /* Remove all highlighting escape sequences in s */
1641 char *
1642 StripHighlight(s)
1643      char *s;
1644 {
1645     static char retbuf[MSG_SIZ];
1646     char *p = retbuf;
1647
1648     while (*s != NULLCHAR) {
1649         while (*s == '\033') {
1650             while (*s != NULLCHAR && !isalpha(*s)) s++;
1651             if (*s != NULLCHAR) s++;
1652         }
1653         while (*s != NULLCHAR && *s != '\033') {
1654             *p++ = *s++;
1655         }
1656     }
1657     *p = NULLCHAR;
1658     return retbuf;
1659 }
1660
1661 char *variantNames[] = VARIANT_NAMES;
1662 char *
1663 VariantName(v)
1664      VariantClass v;
1665 {
1666     return variantNames[v];
1667 }
1668
1669
1670 /* Identify a variant from the strings the chess servers use or the
1671    PGN Variant tag names we use. */
1672 VariantClass
1673 StringToVariant(e)
1674      char *e;
1675 {
1676     char *p;
1677     int wnum = -1;
1678     VariantClass v = VariantNormal;
1679     int i, found = FALSE;
1680     char buf[MSG_SIZ];
1681     int len;
1682
1683     if (!e) return v;
1684
1685     /* [HGM] skip over optional board-size prefixes */
1686     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1687         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1688         while( *e++ != '_');
1689     }
1690
1691     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1692         v = VariantNormal;
1693         found = TRUE;
1694     } else
1695     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1696       if (StrCaseStr(e, variantNames[i])) {
1697         v = (VariantClass) i;
1698         found = TRUE;
1699         break;
1700       }
1701     }
1702
1703     if (!found) {
1704       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1705           || StrCaseStr(e, "wild/fr")
1706           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1707         v = VariantFischeRandom;
1708       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1709                  (i = 1, p = StrCaseStr(e, "w"))) {
1710         p += i;
1711         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1712         if (isdigit(*p)) {
1713           wnum = atoi(p);
1714         } else {
1715           wnum = -1;
1716         }
1717         switch (wnum) {
1718         case 0: /* FICS only, actually */
1719         case 1:
1720           /* Castling legal even if K starts on d-file */
1721           v = VariantWildCastle;
1722           break;
1723         case 2:
1724         case 3:
1725         case 4:
1726           /* Castling illegal even if K & R happen to start in
1727              normal positions. */
1728           v = VariantNoCastle;
1729           break;
1730         case 5:
1731         case 7:
1732         case 8:
1733         case 10:
1734         case 11:
1735         case 12:
1736         case 13:
1737         case 14:
1738         case 15:
1739         case 18:
1740         case 19:
1741           /* Castling legal iff K & R start in normal positions */
1742           v = VariantNormal;
1743           break;
1744         case 6:
1745         case 20:
1746         case 21:
1747           /* Special wilds for position setup; unclear what to do here */
1748           v = VariantLoadable;
1749           break;
1750         case 9:
1751           /* Bizarre ICC game */
1752           v = VariantTwoKings;
1753           break;
1754         case 16:
1755           v = VariantKriegspiel;
1756           break;
1757         case 17:
1758           v = VariantLosers;
1759           break;
1760         case 22:
1761           v = VariantFischeRandom;
1762           break;
1763         case 23:
1764           v = VariantCrazyhouse;
1765           break;
1766         case 24:
1767           v = VariantBughouse;
1768           break;
1769         case 25:
1770           v = Variant3Check;
1771           break;
1772         case 26:
1773           /* Not quite the same as FICS suicide! */
1774           v = VariantGiveaway;
1775           break;
1776         case 27:
1777           v = VariantAtomic;
1778           break;
1779         case 28:
1780           v = VariantShatranj;
1781           break;
1782
1783         /* Temporary names for future ICC types.  The name *will* change in
1784            the next xboard/WinBoard release after ICC defines it. */
1785         case 29:
1786           v = Variant29;
1787           break;
1788         case 30:
1789           v = Variant30;
1790           break;
1791         case 31:
1792           v = Variant31;
1793           break;
1794         case 32:
1795           v = Variant32;
1796           break;
1797         case 33:
1798           v = Variant33;
1799           break;
1800         case 34:
1801           v = Variant34;
1802           break;
1803         case 35:
1804           v = Variant35;
1805           break;
1806         case 36:
1807           v = Variant36;
1808           break;
1809         case 37:
1810           v = VariantShogi;
1811           break;
1812         case 38:
1813           v = VariantXiangqi;
1814           break;
1815         case 39:
1816           v = VariantCourier;
1817           break;
1818         case 40:
1819           v = VariantGothic;
1820           break;
1821         case 41:
1822           v = VariantCapablanca;
1823           break;
1824         case 42:
1825           v = VariantKnightmate;
1826           break;
1827         case 43:
1828           v = VariantFairy;
1829           break;
1830         case 44:
1831           v = VariantCylinder;
1832           break;
1833         case 45:
1834           v = VariantFalcon;
1835           break;
1836         case 46:
1837           v = VariantCapaRandom;
1838           break;
1839         case 47:
1840           v = VariantBerolina;
1841           break;
1842         case 48:
1843           v = VariantJanus;
1844           break;
1845         case 49:
1846           v = VariantSuper;
1847           break;
1848         case 50:
1849           v = VariantGreat;
1850           break;
1851         case -1:
1852           /* Found "wild" or "w" in the string but no number;
1853              must assume it's normal chess. */
1854           v = VariantNormal;
1855           break;
1856         default:
1857           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1858           if( (len > MSG_SIZ) && appData.debugMode )
1859             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1860
1861           DisplayError(buf, 0);
1862           v = VariantUnknown;
1863           break;
1864         }
1865       }
1866     }
1867     if (appData.debugMode) {
1868       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1869               e, wnum, VariantName(v));
1870     }
1871     return v;
1872 }
1873
1874 static int leftover_start = 0, leftover_len = 0;
1875 char star_match[STAR_MATCH_N][MSG_SIZ];
1876
1877 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1878    advance *index beyond it, and set leftover_start to the new value of
1879    *index; else return FALSE.  If pattern contains the character '*', it
1880    matches any sequence of characters not containing '\r', '\n', or the
1881    character following the '*' (if any), and the matched sequence(s) are
1882    copied into star_match.
1883    */
1884 int
1885 looking_at(buf, index, pattern)
1886      char *buf;
1887      int *index;
1888      char *pattern;
1889 {
1890     char *bufp = &buf[*index], *patternp = pattern;
1891     int star_count = 0;
1892     char *matchp = star_match[0];
1893
1894     for (;;) {
1895         if (*patternp == NULLCHAR) {
1896             *index = leftover_start = bufp - buf;
1897             *matchp = NULLCHAR;
1898             return TRUE;
1899         }
1900         if (*bufp == NULLCHAR) return FALSE;
1901         if (*patternp == '*') {
1902             if (*bufp == *(patternp + 1)) {
1903                 *matchp = NULLCHAR;
1904                 matchp = star_match[++star_count];
1905                 patternp += 2;
1906                 bufp++;
1907                 continue;
1908             } else if (*bufp == '\n' || *bufp == '\r') {
1909                 patternp++;
1910                 if (*patternp == NULLCHAR)
1911                   continue;
1912                 else
1913                   return FALSE;
1914             } else {
1915                 *matchp++ = *bufp++;
1916                 continue;
1917             }
1918         }
1919         if (*patternp != *bufp) return FALSE;
1920         patternp++;
1921         bufp++;
1922     }
1923 }
1924
1925 void
1926 SendToPlayer(data, length)
1927      char *data;
1928      int length;
1929 {
1930     int error, outCount;
1931     outCount = OutputToProcess(NoProc, data, length, &error);
1932     if (outCount < length) {
1933         DisplayFatalError(_("Error writing to display"), error, 1);
1934     }
1935 }
1936
1937 void
1938 PackHolding(packed, holding)
1939      char packed[];
1940      char *holding;
1941 {
1942     char *p = holding;
1943     char *q = packed;
1944     int runlength = 0;
1945     int curr = 9999;
1946     do {
1947         if (*p == curr) {
1948             runlength++;
1949         } else {
1950             switch (runlength) {
1951               case 0:
1952                 break;
1953               case 1:
1954                 *q++ = curr;
1955                 break;
1956               case 2:
1957                 *q++ = curr;
1958                 *q++ = curr;
1959                 break;
1960               default:
1961                 sprintf(q, "%d", runlength);
1962                 while (*q) q++;
1963                 *q++ = curr;
1964                 break;
1965             }
1966             runlength = 1;
1967             curr = *p;
1968         }
1969     } while (*p++);
1970     *q = NULLCHAR;
1971 }
1972
1973 /* Telnet protocol requests from the front end */
1974 void
1975 TelnetRequest(ddww, option)
1976      unsigned char ddww, option;
1977 {
1978     unsigned char msg[3];
1979     int outCount, outError;
1980
1981     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1982
1983     if (appData.debugMode) {
1984         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1985         switch (ddww) {
1986           case TN_DO:
1987             ddwwStr = "DO";
1988             break;
1989           case TN_DONT:
1990             ddwwStr = "DONT";
1991             break;
1992           case TN_WILL:
1993             ddwwStr = "WILL";
1994             break;
1995           case TN_WONT:
1996             ddwwStr = "WONT";
1997             break;
1998           default:
1999             ddwwStr = buf1;
2000             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2001             break;
2002         }
2003         switch (option) {
2004           case TN_ECHO:
2005             optionStr = "ECHO";
2006             break;
2007           default:
2008             optionStr = buf2;
2009             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2010             break;
2011         }
2012         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2013     }
2014     msg[0] = TN_IAC;
2015     msg[1] = ddww;
2016     msg[2] = option;
2017     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2018     if (outCount < 3) {
2019         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2020     }
2021 }
2022
2023 void
2024 DoEcho()
2025 {
2026     if (!appData.icsActive) return;
2027     TelnetRequest(TN_DO, TN_ECHO);
2028 }
2029
2030 void
2031 DontEcho()
2032 {
2033     if (!appData.icsActive) return;
2034     TelnetRequest(TN_DONT, TN_ECHO);
2035 }
2036
2037 void
2038 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2039 {
2040     /* put the holdings sent to us by the server on the board holdings area */
2041     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2042     char p;
2043     ChessSquare piece;
2044
2045     if(gameInfo.holdingsWidth < 2)  return;
2046     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2047         return; // prevent overwriting by pre-board holdings
2048
2049     if( (int)lowestPiece >= BlackPawn ) {
2050         holdingsColumn = 0;
2051         countsColumn = 1;
2052         holdingsStartRow = BOARD_HEIGHT-1;
2053         direction = -1;
2054     } else {
2055         holdingsColumn = BOARD_WIDTH-1;
2056         countsColumn = BOARD_WIDTH-2;
2057         holdingsStartRow = 0;
2058         direction = 1;
2059     }
2060
2061     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2062         board[i][holdingsColumn] = EmptySquare;
2063         board[i][countsColumn]   = (ChessSquare) 0;
2064     }
2065     while( (p=*holdings++) != NULLCHAR ) {
2066         piece = CharToPiece( ToUpper(p) );
2067         if(piece == EmptySquare) continue;
2068         /*j = (int) piece - (int) WhitePawn;*/
2069         j = PieceToNumber(piece);
2070         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2071         if(j < 0) continue;               /* should not happen */
2072         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2073         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2074         board[holdingsStartRow+j*direction][countsColumn]++;
2075     }
2076 }
2077
2078
2079 void
2080 VariantSwitch(Board board, VariantClass newVariant)
2081 {
2082    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2083    static Board oldBoard;
2084
2085    startedFromPositionFile = FALSE;
2086    if(gameInfo.variant == newVariant) return;
2087
2088    /* [HGM] This routine is called each time an assignment is made to
2089     * gameInfo.variant during a game, to make sure the board sizes
2090     * are set to match the new variant. If that means adding or deleting
2091     * holdings, we shift the playing board accordingly
2092     * This kludge is needed because in ICS observe mode, we get boards
2093     * of an ongoing game without knowing the variant, and learn about the
2094     * latter only later. This can be because of the move list we requested,
2095     * in which case the game history is refilled from the beginning anyway,
2096     * but also when receiving holdings of a crazyhouse game. In the latter
2097     * case we want to add those holdings to the already received position.
2098     */
2099
2100
2101    if (appData.debugMode) {
2102      fprintf(debugFP, "Switch board from %s to %s\n",
2103              VariantName(gameInfo.variant), VariantName(newVariant));
2104      setbuf(debugFP, NULL);
2105    }
2106    shuffleOpenings = 0;       /* [HGM] shuffle */
2107    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2108    switch(newVariant)
2109      {
2110      case VariantShogi:
2111        newWidth = 9;  newHeight = 9;
2112        gameInfo.holdingsSize = 7;
2113      case VariantBughouse:
2114      case VariantCrazyhouse:
2115        newHoldingsWidth = 2; break;
2116      case VariantGreat:
2117        newWidth = 10;
2118      case VariantSuper:
2119        newHoldingsWidth = 2;
2120        gameInfo.holdingsSize = 8;
2121        break;
2122      case VariantGothic:
2123      case VariantCapablanca:
2124      case VariantCapaRandom:
2125        newWidth = 10;
2126      default:
2127        newHoldingsWidth = gameInfo.holdingsSize = 0;
2128      };
2129
2130    if(newWidth  != gameInfo.boardWidth  ||
2131       newHeight != gameInfo.boardHeight ||
2132       newHoldingsWidth != gameInfo.holdingsWidth ) {
2133
2134      /* shift position to new playing area, if needed */
2135      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2136        for(i=0; i<BOARD_HEIGHT; i++)
2137          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2138            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2139              board[i][j];
2140        for(i=0; i<newHeight; i++) {
2141          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2142          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2143        }
2144      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2145        for(i=0; i<BOARD_HEIGHT; i++)
2146          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2147            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2148              board[i][j];
2149      }
2150      gameInfo.boardWidth  = newWidth;
2151      gameInfo.boardHeight = newHeight;
2152      gameInfo.holdingsWidth = newHoldingsWidth;
2153      gameInfo.variant = newVariant;
2154      InitDrawingSizes(-2, 0);
2155    } else gameInfo.variant = newVariant;
2156    CopyBoard(oldBoard, board);   // remember correctly formatted board
2157      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2158    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2159 }
2160
2161 static int loggedOn = FALSE;
2162
2163 /*-- Game start info cache: --*/
2164 int gs_gamenum;
2165 char gs_kind[MSG_SIZ];
2166 static char player1Name[128] = "";
2167 static char player2Name[128] = "";
2168 static char cont_seq[] = "\n\\   ";
2169 static int player1Rating = -1;
2170 static int player2Rating = -1;
2171 /*----------------------------*/
2172
2173 ColorClass curColor = ColorNormal;
2174 int suppressKibitz = 0;
2175
2176 // [HGM] seekgraph
2177 Boolean soughtPending = FALSE;
2178 Boolean seekGraphUp;
2179 #define MAX_SEEK_ADS 200
2180 #define SQUARE 0x80
2181 char *seekAdList[MAX_SEEK_ADS];
2182 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2183 float tcList[MAX_SEEK_ADS];
2184 char colorList[MAX_SEEK_ADS];
2185 int nrOfSeekAds = 0;
2186 int minRating = 1010, maxRating = 2800;
2187 int hMargin = 10, vMargin = 20, h, w;
2188 extern int squareSize, lineGap;
2189
2190 void
2191 PlotSeekAd(int i)
2192 {
2193         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2194         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2195         if(r < minRating+100 && r >=0 ) r = minRating+100;
2196         if(r > maxRating) r = maxRating;
2197         if(tc < 1.) tc = 1.;
2198         if(tc > 95.) tc = 95.;
2199         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2200         y = ((double)r - minRating)/(maxRating - minRating)
2201             * (h-vMargin-squareSize/8-1) + vMargin;
2202         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2203         if(strstr(seekAdList[i], " u ")) color = 1;
2204         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2205            !strstr(seekAdList[i], "bullet") &&
2206            !strstr(seekAdList[i], "blitz") &&
2207            !strstr(seekAdList[i], "standard") ) color = 2;
2208         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2209         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2210 }
2211
2212 void
2213 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2214 {
2215         char buf[MSG_SIZ], *ext = "";
2216         VariantClass v = StringToVariant(type);
2217         if(strstr(type, "wild")) {
2218             ext = type + 4; // append wild number
2219             if(v == VariantFischeRandom) type = "chess960"; else
2220             if(v == VariantLoadable) type = "setup"; else
2221             type = VariantName(v);
2222         }
2223         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2224         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2225             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2226             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2227             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2228             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2229             seekNrList[nrOfSeekAds] = nr;
2230             zList[nrOfSeekAds] = 0;
2231             seekAdList[nrOfSeekAds++] = StrSave(buf);
2232             if(plot) PlotSeekAd(nrOfSeekAds-1);
2233         }
2234 }
2235
2236 void
2237 EraseSeekDot(int i)
2238 {
2239     int x = xList[i], y = yList[i], d=squareSize/4, k;
2240     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2241     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2242     // now replot every dot that overlapped
2243     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2244         int xx = xList[k], yy = yList[k];
2245         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2246             DrawSeekDot(xx, yy, colorList[k]);
2247     }
2248 }
2249
2250 void
2251 RemoveSeekAd(int nr)
2252 {
2253         int i;
2254         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2255             EraseSeekDot(i);
2256             if(seekAdList[i]) free(seekAdList[i]);
2257             seekAdList[i] = seekAdList[--nrOfSeekAds];
2258             seekNrList[i] = seekNrList[nrOfSeekAds];
2259             ratingList[i] = ratingList[nrOfSeekAds];
2260             colorList[i]  = colorList[nrOfSeekAds];
2261             tcList[i] = tcList[nrOfSeekAds];
2262             xList[i]  = xList[nrOfSeekAds];
2263             yList[i]  = yList[nrOfSeekAds];
2264             zList[i]  = zList[nrOfSeekAds];
2265             seekAdList[nrOfSeekAds] = NULL;
2266             break;
2267         }
2268 }
2269
2270 Boolean
2271 MatchSoughtLine(char *line)
2272 {
2273     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2274     int nr, base, inc, u=0; char dummy;
2275
2276     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2277        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2278        (u=1) &&
2279        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2281         // match: compact and save the line
2282         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2283         return TRUE;
2284     }
2285     return FALSE;
2286 }
2287
2288 int
2289 DrawSeekGraph()
2290 {
2291     int i;
2292     if(!seekGraphUp) return FALSE;
2293     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2294     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2295
2296     DrawSeekBackground(0, 0, w, h);
2297     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2298     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2299     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2300         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2301         yy = h-1-yy;
2302         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2303         if(i%500 == 0) {
2304             char buf[MSG_SIZ];
2305             snprintf(buf, MSG_SIZ, "%d", i);
2306             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2307         }
2308     }
2309     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2310     for(i=1; i<100; i+=(i<10?1:5)) {
2311         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2312         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2313         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2314             char buf[MSG_SIZ];
2315             snprintf(buf, MSG_SIZ, "%d", i);
2316             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2317         }
2318     }
2319     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2320     return TRUE;
2321 }
2322
2323 int SeekGraphClick(ClickType click, int x, int y, int moving)
2324 {
2325     static int lastDown = 0, displayed = 0, lastSecond;
2326     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2327         if(click == Release || moving) return FALSE;
2328         nrOfSeekAds = 0;
2329         soughtPending = TRUE;
2330         SendToICS(ics_prefix);
2331         SendToICS("sought\n"); // should this be "sought all"?
2332     } else { // issue challenge based on clicked ad
2333         int dist = 10000; int i, closest = 0, second = 0;
2334         for(i=0; i<nrOfSeekAds; i++) {
2335             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2336             if(d < dist) { dist = d; closest = i; }
2337             second += (d - zList[i] < 120); // count in-range ads
2338             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2339         }
2340         if(dist < 120) {
2341             char buf[MSG_SIZ];
2342             second = (second > 1);
2343             if(displayed != closest || second != lastSecond) {
2344                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2345                 lastSecond = second; displayed = closest;
2346             }
2347             if(click == Press) {
2348                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2349                 lastDown = closest;
2350                 return TRUE;
2351             } // on press 'hit', only show info
2352             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2353             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2354             SendToICS(ics_prefix);
2355             SendToICS(buf);
2356             return TRUE; // let incoming board of started game pop down the graph
2357         } else if(click == Release) { // release 'miss' is ignored
2358             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2359             if(moving == 2) { // right up-click
2360                 nrOfSeekAds = 0; // refresh graph
2361                 soughtPending = TRUE;
2362                 SendToICS(ics_prefix);
2363                 SendToICS("sought\n"); // should this be "sought all"?
2364             }
2365             return TRUE;
2366         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2367         // press miss or release hit 'pop down' seek graph
2368         seekGraphUp = FALSE;
2369         DrawPosition(TRUE, NULL);
2370     }
2371     return TRUE;
2372 }
2373
2374 void
2375 read_from_ics(isr, closure, data, count, error)
2376      InputSourceRef isr;
2377      VOIDSTAR closure;
2378      char *data;
2379      int count;
2380      int error;
2381 {
2382 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2383 #define STARTED_NONE 0
2384 #define STARTED_MOVES 1
2385 #define STARTED_BOARD 2
2386 #define STARTED_OBSERVE 3
2387 #define STARTED_HOLDINGS 4
2388 #define STARTED_CHATTER 5
2389 #define STARTED_COMMENT 6
2390 #define STARTED_MOVES_NOHIDE 7
2391
2392     static int started = STARTED_NONE;
2393     static char parse[20000];
2394     static int parse_pos = 0;
2395     static char buf[BUF_SIZE + 1];
2396     static int firstTime = TRUE, intfSet = FALSE;
2397     static ColorClass prevColor = ColorNormal;
2398     static int savingComment = FALSE;
2399     static int cmatch = 0; // continuation sequence match
2400     char *bp;
2401     char str[MSG_SIZ];
2402     int i, oldi;
2403     int buf_len;
2404     int next_out;
2405     int tkind;
2406     int backup;    /* [DM] For zippy color lines */
2407     char *p;
2408     char talker[MSG_SIZ]; // [HGM] chat
2409     int channel;
2410
2411     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2412
2413     if (appData.debugMode) {
2414       if (!error) {
2415         fprintf(debugFP, "<ICS: ");
2416         show_bytes(debugFP, data, count);
2417         fprintf(debugFP, "\n");
2418       }
2419     }
2420
2421     if (appData.debugMode) { int f = forwardMostMove;
2422         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2423                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2424                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2425     }
2426     if (count > 0) {
2427         /* If last read ended with a partial line that we couldn't parse,
2428            prepend it to the new read and try again. */
2429         if (leftover_len > 0) {
2430             for (i=0; i<leftover_len; i++)
2431               buf[i] = buf[leftover_start + i];
2432         }
2433
2434     /* copy new characters into the buffer */
2435     bp = buf + leftover_len;
2436     buf_len=leftover_len;
2437     for (i=0; i<count; i++)
2438     {
2439         // ignore these
2440         if (data[i] == '\r')
2441             continue;
2442
2443         // join lines split by ICS?
2444         if (!appData.noJoin)
2445         {
2446             /*
2447                 Joining just consists of finding matches against the
2448                 continuation sequence, and discarding that sequence
2449                 if found instead of copying it.  So, until a match
2450                 fails, there's nothing to do since it might be the
2451                 complete sequence, and thus, something we don't want
2452                 copied.
2453             */
2454             if (data[i] == cont_seq[cmatch])
2455             {
2456                 cmatch++;
2457                 if (cmatch == strlen(cont_seq))
2458                 {
2459                     cmatch = 0; // complete match.  just reset the counter
2460
2461                     /*
2462                         it's possible for the ICS to not include the space
2463                         at the end of the last word, making our [correct]
2464                         join operation fuse two separate words.  the server
2465                         does this when the space occurs at the width setting.
2466                     */
2467                     if (!buf_len || buf[buf_len-1] != ' ')
2468                     {
2469                         *bp++ = ' ';
2470                         buf_len++;
2471                     }
2472                 }
2473                 continue;
2474             }
2475             else if (cmatch)
2476             {
2477                 /*
2478                     match failed, so we have to copy what matched before
2479                     falling through and copying this character.  In reality,
2480                     this will only ever be just the newline character, but
2481                     it doesn't hurt to be precise.
2482                 */
2483                 strncpy(bp, cont_seq, cmatch);
2484                 bp += cmatch;
2485                 buf_len += cmatch;
2486                 cmatch = 0;
2487             }
2488         }
2489
2490         // copy this char
2491         *bp++ = data[i];
2492         buf_len++;
2493     }
2494
2495         buf[buf_len] = NULLCHAR;
2496 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2497         next_out = 0;
2498         leftover_start = 0;
2499
2500         i = 0;
2501         while (i < buf_len) {
2502             /* Deal with part of the TELNET option negotiation
2503                protocol.  We refuse to do anything beyond the
2504                defaults, except that we allow the WILL ECHO option,
2505                which ICS uses to turn off password echoing when we are
2506                directly connected to it.  We reject this option
2507                if localLineEditing mode is on (always on in xboard)
2508                and we are talking to port 23, which might be a real
2509                telnet server that will try to keep WILL ECHO on permanently.
2510              */
2511             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2512                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2513                 unsigned char option;
2514                 oldi = i;
2515                 switch ((unsigned char) buf[++i]) {
2516                   case TN_WILL:
2517                     if (appData.debugMode)
2518                       fprintf(debugFP, "\n<WILL ");
2519                     switch (option = (unsigned char) buf[++i]) {
2520                       case TN_ECHO:
2521                         if (appData.debugMode)
2522                           fprintf(debugFP, "ECHO ");
2523                         /* Reply only if this is a change, according
2524                            to the protocol rules. */
2525                         if (remoteEchoOption) break;
2526                         if (appData.localLineEditing &&
2527                             atoi(appData.icsPort) == TN_PORT) {
2528                             TelnetRequest(TN_DONT, TN_ECHO);
2529                         } else {
2530                             EchoOff();
2531                             TelnetRequest(TN_DO, TN_ECHO);
2532                             remoteEchoOption = TRUE;
2533                         }
2534                         break;
2535                       default:
2536                         if (appData.debugMode)
2537                           fprintf(debugFP, "%d ", option);
2538                         /* Whatever this is, we don't want it. */
2539                         TelnetRequest(TN_DONT, option);
2540                         break;
2541                     }
2542                     break;
2543                   case TN_WONT:
2544                     if (appData.debugMode)
2545                       fprintf(debugFP, "\n<WONT ");
2546                     switch (option = (unsigned char) buf[++i]) {
2547                       case TN_ECHO:
2548                         if (appData.debugMode)
2549                           fprintf(debugFP, "ECHO ");
2550                         /* Reply only if this is a change, according
2551                            to the protocol rules. */
2552                         if (!remoteEchoOption) break;
2553                         EchoOn();
2554                         TelnetRequest(TN_DONT, TN_ECHO);
2555                         remoteEchoOption = FALSE;
2556                         break;
2557                       default:
2558                         if (appData.debugMode)
2559                           fprintf(debugFP, "%d ", (unsigned char) option);
2560                         /* Whatever this is, it must already be turned
2561                            off, because we never agree to turn on
2562                            anything non-default, so according to the
2563                            protocol rules, we don't reply. */
2564                         break;
2565                     }
2566                     break;
2567                   case TN_DO:
2568                     if (appData.debugMode)
2569                       fprintf(debugFP, "\n<DO ");
2570                     switch (option = (unsigned char) buf[++i]) {
2571                       default:
2572                         /* Whatever this is, we refuse to do it. */
2573                         if (appData.debugMode)
2574                           fprintf(debugFP, "%d ", option);
2575                         TelnetRequest(TN_WONT, option);
2576                         break;
2577                     }
2578                     break;
2579                   case TN_DONT:
2580                     if (appData.debugMode)
2581                       fprintf(debugFP, "\n<DONT ");
2582                     switch (option = (unsigned char) buf[++i]) {
2583                       default:
2584                         if (appData.debugMode)
2585                           fprintf(debugFP, "%d ", option);
2586                         /* Whatever this is, we are already not doing
2587                            it, because we never agree to do anything
2588                            non-default, so according to the protocol
2589                            rules, we don't reply. */
2590                         break;
2591                     }
2592                     break;
2593                   case TN_IAC:
2594                     if (appData.debugMode)
2595                       fprintf(debugFP, "\n<IAC ");
2596                     /* Doubled IAC; pass it through */
2597                     i--;
2598                     break;
2599                   default:
2600                     if (appData.debugMode)
2601                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2602                     /* Drop all other telnet commands on the floor */
2603                     break;
2604                 }
2605                 if (oldi > next_out)
2606                   SendToPlayer(&buf[next_out], oldi - next_out);
2607                 if (++i > next_out)
2608                   next_out = i;
2609                 continue;
2610             }
2611
2612             /* OK, this at least will *usually* work */
2613             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2614                 loggedOn = TRUE;
2615             }
2616
2617             if (loggedOn && !intfSet) {
2618                 if (ics_type == ICS_ICC) {
2619                   snprintf(str, MSG_SIZ,
2620                           "/set-quietly interface %s\n/set-quietly style 12\n",
2621                           programVersion);
2622                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2624                 } else if (ics_type == ICS_CHESSNET) {
2625                   snprintf(str, MSG_SIZ, "/style 12\n");
2626                 } else {
2627                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2628                   strcat(str, programVersion);
2629                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2630                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2631                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2632 #ifdef WIN32
2633                   strcat(str, "$iset nohighlight 1\n");
2634 #endif
2635                   strcat(str, "$iset lock 1\n$style 12\n");
2636                 }
2637                 SendToICS(str);
2638                 NotifyFrontendLogin();
2639                 intfSet = TRUE;
2640             }
2641
2642             if (started == STARTED_COMMENT) {
2643                 /* Accumulate characters in comment */
2644                 parse[parse_pos++] = buf[i];
2645                 if (buf[i] == '\n') {
2646                     parse[parse_pos] = NULLCHAR;
2647                     if(chattingPartner>=0) {
2648                         char mess[MSG_SIZ];
2649                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2650                         OutputChatMessage(chattingPartner, mess);
2651                         chattingPartner = -1;
2652                         next_out = i+1; // [HGM] suppress printing in ICS window
2653                     } else
2654                     if(!suppressKibitz) // [HGM] kibitz
2655                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2656                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2657                         int nrDigit = 0, nrAlph = 0, j;
2658                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2659                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2660                         parse[parse_pos] = NULLCHAR;
2661                         // try to be smart: if it does not look like search info, it should go to
2662                         // ICS interaction window after all, not to engine-output window.
2663                         for(j=0; j<parse_pos; j++) { // count letters and digits
2664                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2665                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2666                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2667                         }
2668                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2669                             int depth=0; float score;
2670                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2671                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2672                                 pvInfoList[forwardMostMove-1].depth = depth;
2673                                 pvInfoList[forwardMostMove-1].score = 100*score;
2674                             }
2675                             OutputKibitz(suppressKibitz, parse);
2676                         } else {
2677                             char tmp[MSG_SIZ];
2678                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2679                             SendToPlayer(tmp, strlen(tmp));
2680                         }
2681                         next_out = i+1; // [HGM] suppress printing in ICS window
2682                     }
2683                     started = STARTED_NONE;
2684                 } else {
2685                     /* Don't match patterns against characters in comment */
2686                     i++;
2687                     continue;
2688                 }
2689             }
2690             if (started == STARTED_CHATTER) {
2691                 if (buf[i] != '\n') {
2692                     /* Don't match patterns against characters in chatter */
2693                     i++;
2694                     continue;
2695                 }
2696                 started = STARTED_NONE;
2697                 if(suppressKibitz) next_out = i+1;
2698             }
2699
2700             /* Kludge to deal with rcmd protocol */
2701             if (firstTime && looking_at(buf, &i, "\001*")) {
2702                 DisplayFatalError(&buf[1], 0, 1);
2703                 continue;
2704             } else {
2705                 firstTime = FALSE;
2706             }
2707
2708             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2709                 ics_type = ICS_ICC;
2710                 ics_prefix = "/";
2711                 if (appData.debugMode)
2712                   fprintf(debugFP, "ics_type %d\n", ics_type);
2713                 continue;
2714             }
2715             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2716                 ics_type = ICS_FICS;
2717                 ics_prefix = "$";
2718                 if (appData.debugMode)
2719                   fprintf(debugFP, "ics_type %d\n", ics_type);
2720                 continue;
2721             }
2722             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2723                 ics_type = ICS_CHESSNET;
2724                 ics_prefix = "/";
2725                 if (appData.debugMode)
2726                   fprintf(debugFP, "ics_type %d\n", ics_type);
2727                 continue;
2728             }
2729
2730             if (!loggedOn &&
2731                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2732                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2733                  looking_at(buf, &i, "will be \"*\""))) {
2734               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2735               continue;
2736             }
2737
2738             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2739               char buf[MSG_SIZ];
2740               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2741               DisplayIcsInteractionTitle(buf);
2742               have_set_title = TRUE;
2743             }
2744
2745             /* skip finger notes */
2746             if (started == STARTED_NONE &&
2747                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2748                  (buf[i] == '1' && buf[i+1] == '0')) &&
2749                 buf[i+2] == ':' && buf[i+3] == ' ') {
2750               started = STARTED_CHATTER;
2751               i += 3;
2752               continue;
2753             }
2754
2755             oldi = i;
2756             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2757             if(appData.seekGraph) {
2758                 if(soughtPending && MatchSoughtLine(buf+i)) {
2759                     i = strstr(buf+i, "rated") - buf;
2760                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2761                     next_out = leftover_start = i;
2762                     started = STARTED_CHATTER;
2763                     suppressKibitz = TRUE;
2764                     continue;
2765                 }
2766                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2767                         && looking_at(buf, &i, "* ads displayed")) {
2768                     soughtPending = FALSE;
2769                     seekGraphUp = TRUE;
2770                     DrawSeekGraph();
2771                     continue;
2772                 }
2773                 if(appData.autoRefresh) {
2774                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2775                         int s = (ics_type == ICS_ICC); // ICC format differs
2776                         if(seekGraphUp)
2777                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2778                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2779                         looking_at(buf, &i, "*% "); // eat prompt
2780                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2781                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2782                         next_out = i; // suppress
2783                         continue;
2784                     }
2785                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2786                         char *p = star_match[0];
2787                         while(*p) {
2788                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2789                             while(*p && *p++ != ' '); // next
2790                         }
2791                         looking_at(buf, &i, "*% "); // eat prompt
2792                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2793                         next_out = i;
2794                         continue;
2795                     }
2796                 }
2797             }
2798
2799             /* skip formula vars */
2800             if (started == STARTED_NONE &&
2801                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2802               started = STARTED_CHATTER;
2803               i += 3;
2804               continue;
2805             }
2806
2807             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2808             if (appData.autoKibitz && started == STARTED_NONE &&
2809                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2810                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2811                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2812                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2813                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2814                         suppressKibitz = TRUE;
2815                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2816                         next_out = i;
2817                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2818                                 && (gameMode == IcsPlayingWhite)) ||
2819                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2820                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2821                             started = STARTED_CHATTER; // own kibitz we simply discard
2822                         else {
2823                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2824                             parse_pos = 0; parse[0] = NULLCHAR;
2825                             savingComment = TRUE;
2826                             suppressKibitz = gameMode != IcsObserving ? 2 :
2827                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2828                         }
2829                         continue;
2830                 } else
2831                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2832                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2833                          && atoi(star_match[0])) {
2834                     // suppress the acknowledgements of our own autoKibitz
2835                     char *p;
2836                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2837                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2838                     SendToPlayer(star_match[0], strlen(star_match[0]));
2839                     if(looking_at(buf, &i, "*% ")) // eat prompt
2840                         suppressKibitz = FALSE;
2841                     next_out = i;
2842                     continue;
2843                 }
2844             } // [HGM] kibitz: end of patch
2845
2846             // [HGM] chat: intercept tells by users for which we have an open chat window
2847             channel = -1;
2848             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2849                                            looking_at(buf, &i, "* whispers:") ||
2850                                            looking_at(buf, &i, "* kibitzes:") ||
2851                                            looking_at(buf, &i, "* shouts:") ||
2852                                            looking_at(buf, &i, "* c-shouts:") ||
2853                                            looking_at(buf, &i, "--> * ") ||
2854                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2855                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2856                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2857                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2858                 int p;
2859                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2860                 chattingPartner = -1;
2861
2862                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2863                 for(p=0; p<MAX_CHAT; p++) {
2864                     if(channel == atoi(chatPartner[p])) {
2865                     talker[0] = '['; strcat(talker, "] ");
2866                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2867                     chattingPartner = p; break;
2868                     }
2869                 } else
2870                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2871                 for(p=0; p<MAX_CHAT; p++) {
2872                     if(!strcmp("kibitzes", chatPartner[p])) {
2873                         talker[0] = '['; strcat(talker, "] ");
2874                         chattingPartner = p; break;
2875                     }
2876                 } else
2877                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2878                 for(p=0; p<MAX_CHAT; p++) {
2879                     if(!strcmp("whispers", chatPartner[p])) {
2880                         talker[0] = '['; strcat(talker, "] ");
2881                         chattingPartner = p; break;
2882                     }
2883                 } else
2884                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2885                   if(buf[i-8] == '-' && buf[i-3] == 't')
2886                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2887                     if(!strcmp("c-shouts", chatPartner[p])) {
2888                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2889                         chattingPartner = p; break;
2890                     }
2891                   }
2892                   if(chattingPartner < 0)
2893                   for(p=0; p<MAX_CHAT; p++) {
2894                     if(!strcmp("shouts", chatPartner[p])) {
2895                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2896                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2897                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2898                         chattingPartner = p; break;
2899                     }
2900                   }
2901                 }
2902                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2903                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2904                     talker[0] = 0; Colorize(ColorTell, FALSE);
2905                     chattingPartner = p; break;
2906                 }
2907                 if(chattingPartner<0) i = oldi; else {
2908                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2909                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2910                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2911                     started = STARTED_COMMENT;
2912                     parse_pos = 0; parse[0] = NULLCHAR;
2913                     savingComment = 3 + chattingPartner; // counts as TRUE
2914                     suppressKibitz = TRUE;
2915                     continue;
2916                 }
2917             } // [HGM] chat: end of patch
2918
2919             if (appData.zippyTalk || appData.zippyPlay) {
2920                 /* [DM] Backup address for color zippy lines */
2921                 backup = i;
2922 #if ZIPPY
2923                if (loggedOn == TRUE)
2924                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2925                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2926 #endif
2927             } // [DM] 'else { ' deleted
2928                 if (
2929                     /* Regular tells and says */
2930                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2931                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2932                     looking_at(buf, &i, "* says: ") ||
2933                     /* Don't color "message" or "messages" output */
2934                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2935                     looking_at(buf, &i, "*. * at *:*: ") ||
2936                     looking_at(buf, &i, "--* (*:*): ") ||
2937                     /* Message notifications (same color as tells) */
2938                     looking_at(buf, &i, "* has left a message ") ||
2939                     looking_at(buf, &i, "* just sent you a message:\n") ||
2940                     /* Whispers and kibitzes */
2941                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2942                     looking_at(buf, &i, "* kibitzes: ") ||
2943                     /* Channel tells */
2944                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2945
2946                   if (tkind == 1 && strchr(star_match[0], ':')) {
2947                       /* Avoid "tells you:" spoofs in channels */
2948                      tkind = 3;
2949                   }
2950                   if (star_match[0][0] == NULLCHAR ||
2951                       strchr(star_match[0], ' ') ||
2952                       (tkind == 3 && strchr(star_match[1], ' '))) {
2953                     /* Reject bogus matches */
2954                     i = oldi;
2955                   } else {
2956                     if (appData.colorize) {
2957                       if (oldi > next_out) {
2958                         SendToPlayer(&buf[next_out], oldi - next_out);
2959                         next_out = oldi;
2960                       }
2961                       switch (tkind) {
2962                       case 1:
2963                         Colorize(ColorTell, FALSE);
2964                         curColor = ColorTell;
2965                         break;
2966                       case 2:
2967                         Colorize(ColorKibitz, FALSE);
2968                         curColor = ColorKibitz;
2969                         break;
2970                       case 3:
2971                         p = strrchr(star_match[1], '(');
2972                         if (p == NULL) {
2973                           p = star_match[1];
2974                         } else {
2975                           p++;
2976                         }
2977                         if (atoi(p) == 1) {
2978                           Colorize(ColorChannel1, FALSE);
2979                           curColor = ColorChannel1;
2980                         } else {
2981                           Colorize(ColorChannel, FALSE);
2982                           curColor = ColorChannel;
2983                         }
2984                         break;
2985                       case 5:
2986                         curColor = ColorNormal;
2987                         break;
2988                       }
2989                     }
2990                     if (started == STARTED_NONE && appData.autoComment &&
2991                         (gameMode == IcsObserving ||
2992                          gameMode == IcsPlayingWhite ||
2993                          gameMode == IcsPlayingBlack)) {
2994                       parse_pos = i - oldi;
2995                       memcpy(parse, &buf[oldi], parse_pos);
2996                       parse[parse_pos] = NULLCHAR;
2997                       started = STARTED_COMMENT;
2998                       savingComment = TRUE;
2999                     } else {
3000                       started = STARTED_CHATTER;
3001                       savingComment = FALSE;
3002                     }
3003                     loggedOn = TRUE;
3004                     continue;
3005                   }
3006                 }
3007
3008                 if (looking_at(buf, &i, "* s-shouts: ") ||
3009                     looking_at(buf, &i, "* c-shouts: ")) {
3010                     if (appData.colorize) {
3011                         if (oldi > next_out) {
3012                             SendToPlayer(&buf[next_out], oldi - next_out);
3013                             next_out = oldi;
3014                         }
3015                         Colorize(ColorSShout, FALSE);
3016                         curColor = ColorSShout;
3017                     }
3018                     loggedOn = TRUE;
3019                     started = STARTED_CHATTER;
3020                     continue;
3021                 }
3022
3023                 if (looking_at(buf, &i, "--->")) {
3024                     loggedOn = TRUE;
3025                     continue;
3026                 }
3027
3028                 if (looking_at(buf, &i, "* shouts: ") ||
3029                     looking_at(buf, &i, "--> ")) {
3030                     if (appData.colorize) {
3031                         if (oldi > next_out) {
3032                             SendToPlayer(&buf[next_out], oldi - next_out);
3033                             next_out = oldi;
3034                         }
3035                         Colorize(ColorShout, FALSE);
3036                         curColor = ColorShout;
3037                     }
3038                     loggedOn = TRUE;
3039                     started = STARTED_CHATTER;
3040                     continue;
3041                 }
3042
3043                 if (looking_at( buf, &i, "Challenge:")) {
3044                     if (appData.colorize) {
3045                         if (oldi > next_out) {
3046                             SendToPlayer(&buf[next_out], oldi - next_out);
3047                             next_out = oldi;
3048                         }
3049                         Colorize(ColorChallenge, FALSE);
3050                         curColor = ColorChallenge;
3051                     }
3052                     loggedOn = TRUE;
3053                     continue;
3054                 }
3055
3056                 if (looking_at(buf, &i, "* offers you") ||
3057                     looking_at(buf, &i, "* offers to be") ||
3058                     looking_at(buf, &i, "* would like to") ||
3059                     looking_at(buf, &i, "* requests to") ||
3060                     looking_at(buf, &i, "Your opponent offers") ||
3061                     looking_at(buf, &i, "Your opponent requests")) {
3062
3063                     if (appData.colorize) {
3064                         if (oldi > next_out) {
3065                             SendToPlayer(&buf[next_out], oldi - next_out);
3066                             next_out = oldi;
3067                         }
3068                         Colorize(ColorRequest, FALSE);
3069                         curColor = ColorRequest;
3070                     }
3071                     continue;
3072                 }
3073
3074                 if (looking_at(buf, &i, "* (*) seeking")) {
3075                     if (appData.colorize) {
3076                         if (oldi > next_out) {
3077                             SendToPlayer(&buf[next_out], oldi - next_out);
3078                             next_out = oldi;
3079                         }
3080                         Colorize(ColorSeek, FALSE);
3081                         curColor = ColorSeek;
3082                     }
3083                     continue;
3084             }
3085
3086             if (looking_at(buf, &i, "\\   ")) {
3087                 if (prevColor != ColorNormal) {
3088                     if (oldi > next_out) {
3089                         SendToPlayer(&buf[next_out], oldi - next_out);
3090                         next_out = oldi;
3091                     }
3092                     Colorize(prevColor, TRUE);
3093                     curColor = prevColor;
3094                 }
3095                 if (savingComment) {
3096                     parse_pos = i - oldi;
3097                     memcpy(parse, &buf[oldi], parse_pos);
3098                     parse[parse_pos] = NULLCHAR;
3099                     started = STARTED_COMMENT;
3100                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3101                         chattingPartner = savingComment - 3; // kludge to remember the box
3102                 } else {
3103                     started = STARTED_CHATTER;
3104                 }
3105                 continue;
3106             }
3107
3108             if (looking_at(buf, &i, "Black Strength :") ||
3109                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3110                 looking_at(buf, &i, "<10>") ||
3111                 looking_at(buf, &i, "#@#")) {
3112                 /* Wrong board style */
3113                 loggedOn = TRUE;
3114                 SendToICS(ics_prefix);
3115                 SendToICS("set style 12\n");
3116                 SendToICS(ics_prefix);
3117                 SendToICS("refresh\n");
3118                 continue;
3119             }
3120
3121             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3122                 ICSInitScript();
3123                 have_sent_ICS_logon = 1;
3124                 continue;
3125             }
3126
3127             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3128                 (looking_at(buf, &i, "\n<12> ") ||
3129                  looking_at(buf, &i, "<12> "))) {
3130                 loggedOn = TRUE;
3131                 if (oldi > next_out) {
3132                     SendToPlayer(&buf[next_out], oldi - next_out);
3133                 }
3134                 next_out = i;
3135                 started = STARTED_BOARD;
3136                 parse_pos = 0;
3137                 continue;
3138             }
3139
3140             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3141                 looking_at(buf, &i, "<b1> ")) {
3142                 if (oldi > next_out) {
3143                     SendToPlayer(&buf[next_out], oldi - next_out);
3144                 }
3145                 next_out = i;
3146                 started = STARTED_HOLDINGS;
3147                 parse_pos = 0;
3148                 continue;
3149             }
3150
3151             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3152                 loggedOn = TRUE;
3153                 /* Header for a move list -- first line */
3154
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     switch (gameMode) {
3158                       case IcsIdle:
3159                       case BeginningOfGame:
3160                         /* User typed "moves" or "oldmoves" while we
3161                            were idle.  Pretend we asked for these
3162                            moves and soak them up so user can step
3163                            through them and/or save them.
3164                            */
3165                         Reset(FALSE, TRUE);
3166                         gameMode = IcsObserving;
3167                         ModeHighlight();
3168                         ics_gamenum = -1;
3169                         ics_getting_history = H_GOT_UNREQ_HEADER;
3170                         break;
3171                       case EditGame: /*?*/
3172                       case EditPosition: /*?*/
3173                         /* Should above feature work in these modes too? */
3174                         /* For now it doesn't */
3175                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3176                         break;
3177                       default:
3178                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3179                         break;
3180                     }
3181                     break;
3182                   case H_REQUESTED:
3183                     /* Is this the right one? */
3184                     if (gameInfo.white && gameInfo.black &&
3185                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3186                         strcmp(gameInfo.black, star_match[2]) == 0) {
3187                         /* All is well */
3188                         ics_getting_history = H_GOT_REQ_HEADER;
3189                     }
3190                     break;
3191                   case H_GOT_REQ_HEADER:
3192                   case H_GOT_UNREQ_HEADER:
3193                   case H_GOT_UNWANTED_HEADER:
3194                   case H_GETTING_MOVES:
3195                     /* Should not happen */
3196                     DisplayError(_("Error gathering move list: two headers"), 0);
3197                     ics_getting_history = H_FALSE;
3198                     break;
3199                 }
3200
3201                 /* Save player ratings into gameInfo if needed */
3202                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3203                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3204                     (gameInfo.whiteRating == -1 ||
3205                      gameInfo.blackRating == -1)) {
3206
3207                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3208                     gameInfo.blackRating = string_to_rating(star_match[3]);
3209                     if (appData.debugMode)
3210                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3211                               gameInfo.whiteRating, gameInfo.blackRating);
3212                 }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i,
3217               "* * match, initial time: * minute*, increment: * second")) {
3218                 /* Header for a move list -- second line */
3219                 /* Initial board will follow if this is a wild game */
3220                 if (gameInfo.event != NULL) free(gameInfo.event);
3221                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3222                 gameInfo.event = StrSave(str);
3223                 /* [HGM] we switched variant. Translate boards if needed. */
3224                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3225                 continue;
3226             }
3227
3228             if (looking_at(buf, &i, "Move  ")) {
3229                 /* Beginning of a move list */
3230                 switch (ics_getting_history) {
3231                   case H_FALSE:
3232                     /* Normally should not happen */
3233                     /* Maybe user hit reset while we were parsing */
3234                     break;
3235                   case H_REQUESTED:
3236                     /* Happens if we are ignoring a move list that is not
3237                      * the one we just requested.  Common if the user
3238                      * tries to observe two games without turning off
3239                      * getMoveList */
3240                     break;
3241                   case H_GETTING_MOVES:
3242                     /* Should not happen */
3243                     DisplayError(_("Error gathering move list: nested"), 0);
3244                     ics_getting_history = H_FALSE;
3245                     break;
3246                   case H_GOT_REQ_HEADER:
3247                     ics_getting_history = H_GETTING_MOVES;
3248                     started = STARTED_MOVES;
3249                     parse_pos = 0;
3250                     if (oldi > next_out) {
3251                         SendToPlayer(&buf[next_out], oldi - next_out);
3252                     }
3253                     break;
3254                   case H_GOT_UNREQ_HEADER:
3255                     ics_getting_history = H_GETTING_MOVES;
3256                     started = STARTED_MOVES_NOHIDE;
3257                     parse_pos = 0;
3258                     break;
3259                   case H_GOT_UNWANTED_HEADER:
3260                     ics_getting_history = H_FALSE;
3261                     break;
3262                 }
3263                 continue;
3264             }
3265
3266             if (looking_at(buf, &i, "% ") ||
3267                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3268                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3269                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3270                     soughtPending = FALSE;
3271                     seekGraphUp = TRUE;
3272                     DrawSeekGraph();
3273                 }
3274                 if(suppressKibitz) next_out = i;
3275                 savingComment = FALSE;
3276                 suppressKibitz = 0;
3277                 switch (started) {
3278                   case STARTED_MOVES:
3279                   case STARTED_MOVES_NOHIDE:
3280                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3281                     parse[parse_pos + i - oldi] = NULLCHAR;
3282                     ParseGameHistory(parse);
3283 #if ZIPPY
3284                     if (appData.zippyPlay && first.initDone) {
3285                         FeedMovesToProgram(&first, forwardMostMove);
3286                         if (gameMode == IcsPlayingWhite) {
3287                             if (WhiteOnMove(forwardMostMove)) {
3288                                 if (first.sendTime) {
3289                                   if (first.useColors) {
3290                                     SendToProgram("black\n", &first);
3291                                   }
3292                                   SendTimeRemaining(&first, TRUE);
3293                                 }
3294                                 if (first.useColors) {
3295                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3296                                 }
3297                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3298                                 first.maybeThinking = TRUE;
3299                             } else {
3300                                 if (first.usePlayother) {
3301                                   if (first.sendTime) {
3302                                     SendTimeRemaining(&first, TRUE);
3303                                   }
3304                                   SendToProgram("playother\n", &first);
3305                                   firstMove = FALSE;
3306                                 } else {
3307                                   firstMove = TRUE;
3308                                 }
3309                             }
3310                         } else if (gameMode == IcsPlayingBlack) {
3311                             if (!WhiteOnMove(forwardMostMove)) {
3312                                 if (first.sendTime) {
3313                                   if (first.useColors) {
3314                                     SendToProgram("white\n", &first);
3315                                   }
3316                                   SendTimeRemaining(&first, FALSE);
3317                                 }
3318                                 if (first.useColors) {
3319                                   SendToProgram("black\n", &first);
3320                                 }
3321                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3322                                 first.maybeThinking = TRUE;
3323                             } else {
3324                                 if (first.usePlayother) {
3325                                   if (first.sendTime) {
3326                                     SendTimeRemaining(&first, FALSE);
3327                                   }
3328                                   SendToProgram("playother\n", &first);
3329                                   firstMove = FALSE;
3330                                 } else {
3331                                   firstMove = TRUE;
3332                                 }
3333                             }
3334                         }
3335                     }
3336 #endif
3337                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3338                         /* Moves came from oldmoves or moves command
3339                            while we weren't doing anything else.
3340                            */
3341                         currentMove = forwardMostMove;
3342                         ClearHighlights();/*!!could figure this out*/
3343                         flipView = appData.flipView;
3344                         DrawPosition(TRUE, boards[currentMove]);
3345                         DisplayBothClocks();
3346                         snprintf(str, MSG_SIZ, "%s vs. %s",
3347                                 gameInfo.white, gameInfo.black);
3348                         DisplayTitle(str);
3349                         gameMode = IcsIdle;
3350                     } else {
3351                         /* Moves were history of an active game */
3352                         if (gameInfo.resultDetails != NULL) {
3353                             free(gameInfo.resultDetails);
3354                             gameInfo.resultDetails = NULL;
3355                         }
3356                     }
3357                     HistorySet(parseList, backwardMostMove,
3358                                forwardMostMove, currentMove-1);
3359                     DisplayMove(currentMove - 1);
3360                     if (started == STARTED_MOVES) next_out = i;
3361                     started = STARTED_NONE;
3362                     ics_getting_history = H_FALSE;
3363                     break;
3364
3365                   case STARTED_OBSERVE:
3366                     started = STARTED_NONE;
3367                     SendToICS(ics_prefix);
3368                     SendToICS("refresh\n");
3369                     break;
3370
3371                   default:
3372                     break;
3373                 }
3374                 if(bookHit) { // [HGM] book: simulate book reply
3375                     static char bookMove[MSG_SIZ]; // a bit generous?
3376
3377                     programStats.nodes = programStats.depth = programStats.time =
3378                     programStats.score = programStats.got_only_move = 0;
3379                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3380
3381                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3382                     strcat(bookMove, bookHit);
3383                     HandleMachineMove(bookMove, &first);
3384                 }
3385                 continue;
3386             }
3387
3388             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3389                  started == STARTED_HOLDINGS ||
3390                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3391                 /* Accumulate characters in move list or board */
3392                 parse[parse_pos++] = buf[i];
3393             }
3394
3395             /* Start of game messages.  Mostly we detect start of game
3396                when the first board image arrives.  On some versions
3397                of the ICS, though, we need to do a "refresh" after starting
3398                to observe in order to get the current board right away. */
3399             if (looking_at(buf, &i, "Adding game * to observation list")) {
3400                 started = STARTED_OBSERVE;
3401                 continue;
3402             }
3403
3404             /* Handle auto-observe */
3405             if (appData.autoObserve &&
3406                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3407                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3408                 char *player;
3409                 /* Choose the player that was highlighted, if any. */
3410                 if (star_match[0][0] == '\033' ||
3411                     star_match[1][0] != '\033') {
3412                     player = star_match[0];
3413                 } else {
3414                     player = star_match[2];
3415                 }
3416                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3417                         ics_prefix, StripHighlightAndTitle(player));
3418                 SendToICS(str);
3419
3420                 /* Save ratings from notify string */
3421                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3422                 player1Rating = string_to_rating(star_match[1]);
3423                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3424                 player2Rating = string_to_rating(star_match[3]);
3425
3426                 if (appData.debugMode)
3427                   fprintf(debugFP,
3428                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3429                           player1Name, player1Rating,
3430                           player2Name, player2Rating);
3431
3432                 continue;
3433             }
3434
3435             /* Deal with automatic examine mode after a game,
3436                and with IcsObserving -> IcsExamining transition */
3437             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3438                 looking_at(buf, &i, "has made you an examiner of game *")) {
3439
3440                 int gamenum = atoi(star_match[0]);
3441                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3442                     gamenum == ics_gamenum) {
3443                     /* We were already playing or observing this game;
3444                        no need to refetch history */
3445                     gameMode = IcsExamining;
3446                     if (pausing) {
3447                         pauseExamForwardMostMove = forwardMostMove;
3448                     } else if (currentMove < forwardMostMove) {
3449                         ForwardInner(forwardMostMove);
3450                     }
3451                 } else {
3452                     /* I don't think this case really can happen */
3453                     SendToICS(ics_prefix);
3454                     SendToICS("refresh\n");
3455                 }
3456                 continue;
3457             }
3458
3459             /* Error messages */
3460 //          if (ics_user_moved) {
3461             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3462                 if (looking_at(buf, &i, "Illegal move") ||
3463                     looking_at(buf, &i, "Not a legal move") ||
3464                     looking_at(buf, &i, "Your king is in check") ||
3465                     looking_at(buf, &i, "It isn't your turn") ||
3466                     looking_at(buf, &i, "It is not your move")) {
3467                     /* Illegal move */
3468                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3469                         currentMove = forwardMostMove-1;
3470                         DisplayMove(currentMove - 1); /* before DMError */
3471                         DrawPosition(FALSE, boards[currentMove]);
3472                         SwitchClocks(forwardMostMove-1); // [HGM] race
3473                         DisplayBothClocks();
3474                     }
3475                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3476                     ics_user_moved = 0;
3477                     continue;
3478                 }
3479             }
3480
3481             if (looking_at(buf, &i, "still have time") ||
3482                 looking_at(buf, &i, "not out of time") ||
3483                 looking_at(buf, &i, "either player is out of time") ||
3484                 looking_at(buf, &i, "has timeseal; checking")) {
3485                 /* We must have called his flag a little too soon */
3486                 whiteFlag = blackFlag = FALSE;
3487                 continue;
3488             }
3489
3490             if (looking_at(buf, &i, "added * seconds to") ||
3491                 looking_at(buf, &i, "seconds were added to")) {
3492                 /* Update the clocks */
3493                 SendToICS(ics_prefix);
3494                 SendToICS("refresh\n");
3495                 continue;
3496             }
3497
3498             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3499                 ics_clock_paused = TRUE;
3500                 StopClocks();
3501                 continue;
3502             }
3503
3504             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3505                 ics_clock_paused = FALSE;
3506                 StartClocks();
3507                 continue;
3508             }
3509
3510             /* Grab player ratings from the Creating: message.
3511                Note we have to check for the special case when
3512                the ICS inserts things like [white] or [black]. */
3513             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3514                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3515                 /* star_matches:
3516                    0    player 1 name (not necessarily white)
3517                    1    player 1 rating
3518                    2    empty, white, or black (IGNORED)
3519                    3    player 2 name (not necessarily black)
3520                    4    player 2 rating
3521
3522                    The names/ratings are sorted out when the game
3523                    actually starts (below).
3524                 */
3525                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3526                 player1Rating = string_to_rating(star_match[1]);
3527                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3528                 player2Rating = string_to_rating(star_match[4]);
3529
3530                 if (appData.debugMode)
3531                   fprintf(debugFP,
3532                           "Ratings from 'Creating:' %s %d, %s %d\n",
3533                           player1Name, player1Rating,
3534                           player2Name, player2Rating);
3535
3536                 continue;
3537             }
3538
3539             /* Improved generic start/end-of-game messages */
3540             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3541                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3542                 /* If tkind == 0: */
3543                 /* star_match[0] is the game number */
3544                 /*           [1] is the white player's name */
3545                 /*           [2] is the black player's name */
3546                 /* For end-of-game: */
3547                 /*           [3] is the reason for the game end */
3548                 /*           [4] is a PGN end game-token, preceded by " " */
3549                 /* For start-of-game: */
3550                 /*           [3] begins with "Creating" or "Continuing" */
3551                 /*           [4] is " *" or empty (don't care). */
3552                 int gamenum = atoi(star_match[0]);
3553                 char *whitename, *blackname, *why, *endtoken;
3554                 ChessMove endtype = EndOfFile;
3555
3556                 if (tkind == 0) {
3557                   whitename = star_match[1];
3558                   blackname = star_match[2];
3559                   why = star_match[3];
3560                   endtoken = star_match[4];
3561                 } else {
3562                   whitename = star_match[1];
3563                   blackname = star_match[3];
3564                   why = star_match[5];
3565                   endtoken = star_match[6];
3566                 }
3567
3568                 /* Game start messages */
3569                 if (strncmp(why, "Creating ", 9) == 0 ||
3570                     strncmp(why, "Continuing ", 11) == 0) {
3571                     gs_gamenum = gamenum;
3572                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3573                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3574 #if ZIPPY
3575                     if (appData.zippyPlay) {
3576                         ZippyGameStart(whitename, blackname);
3577                     }
3578 #endif /*ZIPPY*/
3579                     partnerBoardValid = FALSE; // [HGM] bughouse
3580                     continue;
3581                 }
3582
3583                 /* Game end messages */
3584                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3585                     ics_gamenum != gamenum) {
3586                     continue;
3587                 }
3588                 while (endtoken[0] == ' ') endtoken++;
3589                 switch (endtoken[0]) {
3590                   case '*':
3591                   default:
3592                     endtype = GameUnfinished;
3593                     break;
3594                   case '0':
3595                     endtype = BlackWins;
3596                     break;
3597                   case '1':
3598                     if (endtoken[1] == '/')
3599                       endtype = GameIsDrawn;
3600                     else
3601                       endtype = WhiteWins;
3602                     break;
3603                 }
3604                 GameEnds(endtype, why, GE_ICS);
3605 #if ZIPPY
3606                 if (appData.zippyPlay && first.initDone) {
3607                     ZippyGameEnd(endtype, why);
3608                     if (first.pr == NULL) {
3609                       /* Start the next process early so that we'll
3610                          be ready for the next challenge */
3611                       StartChessProgram(&first);
3612                     }
3613                     /* Send "new" early, in case this command takes
3614                        a long time to finish, so that we'll be ready
3615                        for the next challenge. */
3616                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3617                     Reset(TRUE, TRUE);
3618                 }
3619 #endif /*ZIPPY*/
3620                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3621                 continue;
3622             }
3623
3624             if (looking_at(buf, &i, "Removing game * from observation") ||
3625                 looking_at(buf, &i, "no longer observing game *") ||
3626                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3627                 if (gameMode == IcsObserving &&
3628                     atoi(star_match[0]) == ics_gamenum)
3629                   {
3630                       /* icsEngineAnalyze */
3631                       if (appData.icsEngineAnalyze) {
3632                             ExitAnalyzeMode();
3633                             ModeHighlight();
3634                       }
3635                       StopClocks();
3636                       gameMode = IcsIdle;
3637                       ics_gamenum = -1;
3638                       ics_user_moved = FALSE;
3639                   }
3640                 continue;
3641             }
3642
3643             if (looking_at(buf, &i, "no longer examining game *")) {
3644                 if (gameMode == IcsExamining &&
3645                     atoi(star_match[0]) == ics_gamenum)
3646                   {
3647                       gameMode = IcsIdle;
3648                       ics_gamenum = -1;
3649                       ics_user_moved = FALSE;
3650                   }
3651                 continue;
3652             }
3653
3654             /* Advance leftover_start past any newlines we find,
3655                so only partial lines can get reparsed */
3656             if (looking_at(buf, &i, "\n")) {
3657                 prevColor = curColor;
3658                 if (curColor != ColorNormal) {
3659                     if (oldi > next_out) {
3660                         SendToPlayer(&buf[next_out], oldi - next_out);
3661                         next_out = oldi;
3662                     }
3663                     Colorize(ColorNormal, FALSE);
3664                     curColor = ColorNormal;
3665                 }
3666                 if (started == STARTED_BOARD) {
3667                     started = STARTED_NONE;
3668                     parse[parse_pos] = NULLCHAR;
3669                     ParseBoard12(parse);
3670                     ics_user_moved = 0;
3671
3672                     /* Send premove here */
3673                     if (appData.premove) {
3674                       char str[MSG_SIZ];
3675                       if (currentMove == 0 &&
3676                           gameMode == IcsPlayingWhite &&
3677                           appData.premoveWhite) {
3678                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3679                         if (appData.debugMode)
3680                           fprintf(debugFP, "Sending premove:\n");
3681                         SendToICS(str);
3682                       } else if (currentMove == 1 &&
3683                                  gameMode == IcsPlayingBlack &&
3684                                  appData.premoveBlack) {
3685                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3686                         if (appData.debugMode)
3687                           fprintf(debugFP, "Sending premove:\n");
3688                         SendToICS(str);
3689                       } else if (gotPremove) {
3690                         gotPremove = 0;
3691                         ClearPremoveHighlights();
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                           UserMoveEvent(premoveFromX, premoveFromY,
3695                                         premoveToX, premoveToY,
3696                                         premovePromoChar);
3697                       }
3698                     }
3699
3700                     /* Usually suppress following prompt */
3701                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3702                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3703                         if (looking_at(buf, &i, "*% ")) {
3704                             savingComment = FALSE;
3705                             suppressKibitz = 0;
3706                         }
3707                     }
3708                     next_out = i;
3709                 } else if (started == STARTED_HOLDINGS) {
3710                     int gamenum;
3711                     char new_piece[MSG_SIZ];
3712                     started = STARTED_NONE;
3713                     parse[parse_pos] = NULLCHAR;
3714                     if (appData.debugMode)
3715                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3716                                                         parse, currentMove);
3717                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3718                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3719                         if (gameInfo.variant == VariantNormal) {
3720                           /* [HGM] We seem to switch variant during a game!
3721                            * Presumably no holdings were displayed, so we have
3722                            * to move the position two files to the right to
3723                            * create room for them!
3724                            */
3725                           VariantClass newVariant;
3726                           switch(gameInfo.boardWidth) { // base guess on board width
3727                                 case 9:  newVariant = VariantShogi; break;
3728                                 case 10: newVariant = VariantGreat; break;
3729                                 default: newVariant = VariantCrazyhouse; break;
3730                           }
3731                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3732                           /* Get a move list just to see the header, which
3733                              will tell us whether this is really bug or zh */
3734                           if (ics_getting_history == H_FALSE) {
3735                             ics_getting_history = H_REQUESTED;
3736                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3737                             SendToICS(str);
3738                           }
3739                         }
3740                         new_piece[0] = NULLCHAR;
3741                         sscanf(parse, "game %d white [%s black [%s <- %s",
3742                                &gamenum, white_holding, black_holding,
3743                                new_piece);
3744                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3745                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3746                         /* [HGM] copy holdings to board holdings area */
3747                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3748                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3749                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3750 #if ZIPPY
3751                         if (appData.zippyPlay && first.initDone) {
3752                             ZippyHoldings(white_holding, black_holding,
3753                                           new_piece);
3754                         }
3755 #endif /*ZIPPY*/
3756                         if (tinyLayout || smallLayout) {
3757                             char wh[16], bh[16];
3758                             PackHolding(wh, white_holding);
3759                             PackHolding(bh, black_holding);
3760                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3761                                     gameInfo.white, gameInfo.black);
3762                         } else {
3763                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3764                                     gameInfo.white, white_holding,
3765                                     gameInfo.black, black_holding);
3766                         }
3767                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3768                         DrawPosition(FALSE, boards[currentMove]);
3769                         DisplayTitle(str);
3770                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3771                         sscanf(parse, "game %d white [%s black [%s <- %s",
3772                                &gamenum, white_holding, black_holding,
3773                                new_piece);
3774                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3775                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3776                         /* [HGM] copy holdings to partner-board holdings area */
3777                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3778                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3779                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3780                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3781                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3782                       }
3783                     }
3784                     /* Suppress following prompt */
3785                     if (looking_at(buf, &i, "*% ")) {
3786                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3787                         savingComment = FALSE;
3788                         suppressKibitz = 0;
3789                     }
3790                     next_out = i;
3791                 }
3792                 continue;
3793             }
3794
3795             i++;                /* skip unparsed character and loop back */
3796         }
3797
3798         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3799 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3800 //          SendToPlayer(&buf[next_out], i - next_out);
3801             started != STARTED_HOLDINGS && leftover_start > next_out) {
3802             SendToPlayer(&buf[next_out], leftover_start - next_out);
3803             next_out = i;
3804         }
3805
3806         leftover_len = buf_len - leftover_start;
3807         /* if buffer ends with something we couldn't parse,
3808            reparse it after appending the next read */
3809
3810     } else if (count == 0) {
3811         RemoveInputSource(isr);
3812         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3813     } else {
3814         DisplayFatalError(_("Error reading from ICS"), error, 1);
3815     }
3816 }
3817
3818
3819 /* Board style 12 looks like this:
3820
3821    <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
3822
3823  * The "<12> " is stripped before it gets to this routine.  The two
3824  * trailing 0's (flip state and clock ticking) are later addition, and
3825  * some chess servers may not have them, or may have only the first.
3826  * Additional trailing fields may be added in the future.
3827  */
3828
3829 #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"
3830
3831 #define RELATION_OBSERVING_PLAYED    0
3832 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3833 #define RELATION_PLAYING_MYMOVE      1
3834 #define RELATION_PLAYING_NOTMYMOVE  -1
3835 #define RELATION_EXAMINING           2
3836 #define RELATION_ISOLATED_BOARD     -3
3837 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3838
3839 void
3840 ParseBoard12(string)
3841      char *string;
3842 {
3843     GameMode newGameMode;
3844     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3845     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3846     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3847     char to_play, board_chars[200];
3848     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3849     char black[32], white[32];
3850     Board board;
3851     int prevMove = currentMove;
3852     int ticking = 2;
3853     ChessMove moveType;
3854     int fromX, fromY, toX, toY;
3855     char promoChar;
3856     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3857     char *bookHit = NULL; // [HGM] book
3858     Boolean weird = FALSE, reqFlag = FALSE;
3859
3860     fromX = fromY = toX = toY = -1;
3861
3862     newGame = FALSE;
3863
3864     if (appData.debugMode)
3865       fprintf(debugFP, _("Parsing board: %s\n"), string);
3866
3867     move_str[0] = NULLCHAR;
3868     elapsed_time[0] = NULLCHAR;
3869     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3870         int  i = 0, j;
3871         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3872             if(string[i] == ' ') { ranks++; files = 0; }
3873             else files++;
3874             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3875             i++;
3876         }
3877         for(j = 0; j <i; j++) board_chars[j] = string[j];
3878         board_chars[i] = '\0';
3879         string += i + 1;
3880     }
3881     n = sscanf(string, PATTERN, &to_play, &double_push,
3882                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3883                &gamenum, white, black, &relation, &basetime, &increment,
3884                &white_stren, &black_stren, &white_time, &black_time,
3885                &moveNum, str, elapsed_time, move_str, &ics_flip,
3886                &ticking);
3887
3888     if (n < 21) {
3889         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3890         DisplayError(str, 0);
3891         return;
3892     }
3893
3894     /* Convert the move number to internal form */
3895     moveNum = (moveNum - 1) * 2;
3896     if (to_play == 'B') moveNum++;
3897     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3898       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3899                         0, 1);
3900       return;
3901     }
3902
3903     switch (relation) {
3904       case RELATION_OBSERVING_PLAYED:
3905       case RELATION_OBSERVING_STATIC:
3906         if (gamenum == -1) {
3907             /* Old ICC buglet */
3908             relation = RELATION_OBSERVING_STATIC;
3909         }
3910         newGameMode = IcsObserving;
3911         break;
3912       case RELATION_PLAYING_MYMOVE:
3913       case RELATION_PLAYING_NOTMYMOVE:
3914         newGameMode =
3915           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3916             IcsPlayingWhite : IcsPlayingBlack;
3917         break;
3918       case RELATION_EXAMINING:
3919         newGameMode = IcsExamining;
3920         break;
3921       case RELATION_ISOLATED_BOARD:
3922       default:
3923         /* Just display this board.  If user was doing something else,
3924            we will forget about it until the next board comes. */
3925         newGameMode = IcsIdle;
3926         break;
3927       case RELATION_STARTING_POSITION:
3928         newGameMode = gameMode;
3929         break;
3930     }
3931
3932     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3933          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3934       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3935       char *toSqr;
3936       for (k = 0; k < ranks; k++) {
3937         for (j = 0; j < files; j++)
3938           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3939         if(gameInfo.holdingsWidth > 1) {
3940              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3941              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3942         }
3943       }
3944       CopyBoard(partnerBoard, board);
3945       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3946         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3947         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3948       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3949       if(toSqr = strchr(str, '-')) {
3950         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3951         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3952       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3953       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3954       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3955       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3956       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3957       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3958                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3959       DisplayMessage(partnerStatus, "");
3960         partnerBoardValid = TRUE;
3961       return;
3962     }
3963
3964     /* Modify behavior for initial board display on move listing
3965        of wild games.
3966        */
3967     switch (ics_getting_history) {
3968       case H_FALSE:
3969       case H_REQUESTED:
3970         break;
3971       case H_GOT_REQ_HEADER:
3972       case H_GOT_UNREQ_HEADER:
3973         /* This is the initial position of the current game */
3974         gamenum = ics_gamenum;
3975         moveNum = 0;            /* old ICS bug workaround */
3976         if (to_play == 'B') {
3977           startedFromSetupPosition = TRUE;
3978           blackPlaysFirst = TRUE;
3979           moveNum = 1;
3980           if (forwardMostMove == 0) forwardMostMove = 1;
3981           if (backwardMostMove == 0) backwardMostMove = 1;
3982           if (currentMove == 0) currentMove = 1;
3983         }
3984         newGameMode = gameMode;
3985         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3986         break;
3987       case H_GOT_UNWANTED_HEADER:
3988         /* This is an initial board that we don't want */
3989         return;
3990       case H_GETTING_MOVES:
3991         /* Should not happen */
3992         DisplayError(_("Error gathering move list: extra board"), 0);
3993         ics_getting_history = H_FALSE;
3994         return;
3995     }
3996
3997    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3998                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
3999      /* [HGM] We seem to have switched variant unexpectedly
4000       * Try to guess new variant from board size
4001       */
4002           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4003           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4004           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4005           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4006           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4007           if(!weird) newVariant = VariantNormal;
4008           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4009           /* Get a move list just to see the header, which
4010              will tell us whether this is really bug or zh */
4011           if (ics_getting_history == H_FALSE) {
4012             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4013             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4014             SendToICS(str);
4015           }
4016     }
4017
4018     /* Take action if this is the first board of a new game, or of a
4019        different game than is currently being displayed.  */
4020     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4021         relation == RELATION_ISOLATED_BOARD) {
4022
4023         /* Forget the old game and get the history (if any) of the new one */
4024         if (gameMode != BeginningOfGame) {
4025           Reset(TRUE, TRUE);
4026         }
4027         newGame = TRUE;
4028         if (appData.autoRaiseBoard) BoardToTop();
4029         prevMove = -3;
4030         if (gamenum == -1) {
4031             newGameMode = IcsIdle;
4032         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4033                    appData.getMoveList && !reqFlag) {
4034             /* Need to get game history */
4035             ics_getting_history = H_REQUESTED;
4036             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4037             SendToICS(str);
4038         }
4039
4040         /* Initially flip the board to have black on the bottom if playing
4041            black or if the ICS flip flag is set, but let the user change
4042            it with the Flip View button. */
4043         flipView = appData.autoFlipView ?
4044           (newGameMode == IcsPlayingBlack) || ics_flip :
4045           appData.flipView;
4046
4047         /* Done with values from previous mode; copy in new ones */
4048         gameMode = newGameMode;
4049         ModeHighlight();
4050         ics_gamenum = gamenum;
4051         if (gamenum == gs_gamenum) {
4052             int klen = strlen(gs_kind);
4053             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4054             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4055             gameInfo.event = StrSave(str);
4056         } else {
4057             gameInfo.event = StrSave("ICS game");
4058         }
4059         gameInfo.site = StrSave(appData.icsHost);
4060         gameInfo.date = PGNDate();
4061         gameInfo.round = StrSave("-");
4062         gameInfo.white = StrSave(white);
4063         gameInfo.black = StrSave(black);
4064         timeControl = basetime * 60 * 1000;
4065         timeControl_2 = 0;
4066         timeIncrement = increment * 1000;
4067         movesPerSession = 0;
4068         gameInfo.timeControl = TimeControlTagValue();
4069         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4070   if (appData.debugMode) {
4071     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4072     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4073     setbuf(debugFP, NULL);
4074   }
4075
4076         gameInfo.outOfBook = NULL;
4077
4078         /* Do we have the ratings? */
4079         if (strcmp(player1Name, white) == 0 &&
4080             strcmp(player2Name, black) == 0) {
4081             if (appData.debugMode)
4082               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4083                       player1Rating, player2Rating);
4084             gameInfo.whiteRating = player1Rating;
4085             gameInfo.blackRating = player2Rating;
4086         } else if (strcmp(player2Name, white) == 0 &&
4087                    strcmp(player1Name, black) == 0) {
4088             if (appData.debugMode)
4089               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4090                       player2Rating, player1Rating);
4091             gameInfo.whiteRating = player2Rating;
4092             gameInfo.blackRating = player1Rating;
4093         }
4094         player1Name[0] = player2Name[0] = NULLCHAR;
4095
4096         /* Silence shouts if requested */
4097         if (appData.quietPlay &&
4098             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4099             SendToICS(ics_prefix);
4100             SendToICS("set shout 0\n");
4101         }
4102     }
4103
4104     /* Deal with midgame name changes */
4105     if (!newGame) {
4106         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4107             if (gameInfo.white) free(gameInfo.white);
4108             gameInfo.white = StrSave(white);
4109         }
4110         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4111             if (gameInfo.black) free(gameInfo.black);
4112             gameInfo.black = StrSave(black);
4113         }
4114     }
4115
4116     /* Throw away game result if anything actually changes in examine mode */
4117     if (gameMode == IcsExamining && !newGame) {
4118         gameInfo.result = GameUnfinished;
4119         if (gameInfo.resultDetails != NULL) {
4120             free(gameInfo.resultDetails);
4121             gameInfo.resultDetails = NULL;
4122         }
4123     }
4124
4125     /* In pausing && IcsExamining mode, we ignore boards coming
4126        in if they are in a different variation than we are. */
4127     if (pauseExamInvalid) return;
4128     if (pausing && gameMode == IcsExamining) {
4129         if (moveNum <= pauseExamForwardMostMove) {
4130             pauseExamInvalid = TRUE;
4131             forwardMostMove = pauseExamForwardMostMove;
4132             return;
4133         }
4134     }
4135
4136   if (appData.debugMode) {
4137     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4138   }
4139     /* Parse the board */
4140     for (k = 0; k < ranks; k++) {
4141       for (j = 0; j < files; j++)
4142         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4143       if(gameInfo.holdingsWidth > 1) {
4144            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4145            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4146       }
4147     }
4148     CopyBoard(boards[moveNum], board);
4149     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4150     if (moveNum == 0) {
4151         startedFromSetupPosition =
4152           !CompareBoards(board, initialPosition);
4153         if(startedFromSetupPosition)
4154             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4155     }
4156
4157     /* [HGM] Set castling rights. Take the outermost Rooks,
4158        to make it also work for FRC opening positions. Note that board12
4159        is really defective for later FRC positions, as it has no way to
4160        indicate which Rook can castle if they are on the same side of King.
4161        For the initial position we grant rights to the outermost Rooks,
4162        and remember thos rights, and we then copy them on positions
4163        later in an FRC game. This means WB might not recognize castlings with
4164        Rooks that have moved back to their original position as illegal,
4165        but in ICS mode that is not its job anyway.
4166     */
4167     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4168     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4169
4170         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4171             if(board[0][i] == WhiteRook) j = i;
4172         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4173         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4174             if(board[0][i] == WhiteRook) j = i;
4175         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4176         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4177             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4178         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4179         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4180             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4181         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182
4183         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4184         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4185             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4186         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187             if(board[BOARD_HEIGHT-1][k] == bKing)
4188                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4189         if(gameInfo.variant == VariantTwoKings) {
4190             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4191             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4192             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4193         }
4194     } else { int r;
4195         r = boards[moveNum][CASTLING][0] = initialRights[0];
4196         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4197         r = boards[moveNum][CASTLING][1] = initialRights[1];
4198         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4199         r = boards[moveNum][CASTLING][3] = initialRights[3];
4200         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4201         r = boards[moveNum][CASTLING][4] = initialRights[4];
4202         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4203         /* wildcastle kludge: always assume King has rights */
4204         r = boards[moveNum][CASTLING][2] = initialRights[2];
4205         r = boards[moveNum][CASTLING][5] = initialRights[5];
4206     }
4207     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4208     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4209
4210
4211     if (ics_getting_history == H_GOT_REQ_HEADER ||
4212         ics_getting_history == H_GOT_UNREQ_HEADER) {
4213         /* This was an initial position from a move list, not
4214            the current position */
4215         return;
4216     }
4217
4218     /* Update currentMove and known move number limits */
4219     newMove = newGame || moveNum > forwardMostMove;
4220
4221     if (newGame) {
4222         forwardMostMove = backwardMostMove = currentMove = moveNum;
4223         if (gameMode == IcsExamining && moveNum == 0) {
4224           /* Workaround for ICS limitation: we are not told the wild
4225              type when starting to examine a game.  But if we ask for
4226              the move list, the move list header will tell us */
4227             ics_getting_history = H_REQUESTED;
4228             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229             SendToICS(str);
4230         }
4231     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4232                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4233 #if ZIPPY
4234         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4235         /* [HGM] applied this also to an engine that is silently watching        */
4236         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4237             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4238             gameInfo.variant == currentlyInitializedVariant) {
4239           takeback = forwardMostMove - moveNum;
4240           for (i = 0; i < takeback; i++) {
4241             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4242             SendToProgram("undo\n", &first);
4243           }
4244         }
4245 #endif
4246
4247         forwardMostMove = moveNum;
4248         if (!pausing || currentMove > forwardMostMove)
4249           currentMove = forwardMostMove;
4250     } else {
4251         /* New part of history that is not contiguous with old part */
4252         if (pausing && gameMode == IcsExamining) {
4253             pauseExamInvalid = TRUE;
4254             forwardMostMove = pauseExamForwardMostMove;
4255             return;
4256         }
4257         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4258 #if ZIPPY
4259             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4260                 // [HGM] when we will receive the move list we now request, it will be
4261                 // fed to the engine from the first move on. So if the engine is not
4262                 // in the initial position now, bring it there.
4263                 InitChessProgram(&first, 0);
4264             }
4265 #endif
4266             ics_getting_history = H_REQUESTED;
4267             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4268             SendToICS(str);
4269         }
4270         forwardMostMove = backwardMostMove = currentMove = moveNum;
4271     }
4272
4273     /* Update the clocks */
4274     if (strchr(elapsed_time, '.')) {
4275       /* Time is in ms */
4276       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4277       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4278     } else {
4279       /* Time is in seconds */
4280       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4281       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4282     }
4283
4284
4285 #if ZIPPY
4286     if (appData.zippyPlay && newGame &&
4287         gameMode != IcsObserving && gameMode != IcsIdle &&
4288         gameMode != IcsExamining)
4289       ZippyFirstBoard(moveNum, basetime, increment);
4290 #endif
4291
4292     /* Put the move on the move list, first converting
4293        to canonical algebraic form. */
4294     if (moveNum > 0) {
4295   if (appData.debugMode) {
4296     if (appData.debugMode) { int f = forwardMostMove;
4297         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4298                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4299                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4300     }
4301     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4302     fprintf(debugFP, "moveNum = %d\n", moveNum);
4303     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4304     setbuf(debugFP, NULL);
4305   }
4306         if (moveNum <= backwardMostMove) {
4307             /* We don't know what the board looked like before
4308                this move.  Punt. */
4309           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4310             strcat(parseList[moveNum - 1], " ");
4311             strcat(parseList[moveNum - 1], elapsed_time);
4312             moveList[moveNum - 1][0] = NULLCHAR;
4313         } else if (strcmp(move_str, "none") == 0) {
4314             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4315             /* Again, we don't know what the board looked like;
4316                this is really the start of the game. */
4317             parseList[moveNum - 1][0] = NULLCHAR;
4318             moveList[moveNum - 1][0] = NULLCHAR;
4319             backwardMostMove = moveNum;
4320             startedFromSetupPosition = TRUE;
4321             fromX = fromY = toX = toY = -1;
4322         } else {
4323           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4324           //                 So we parse the long-algebraic move string in stead of the SAN move
4325           int valid; char buf[MSG_SIZ], *prom;
4326
4327           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4328                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4329           // str looks something like "Q/a1-a2"; kill the slash
4330           if(str[1] == '/')
4331             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4332           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4333           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4334                 strcat(buf, prom); // long move lacks promo specification!
4335           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4336                 if(appData.debugMode)
4337                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4338                 safeStrCpy(move_str, buf, MSG_SIZ);
4339           }
4340           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4341                                 &fromX, &fromY, &toX, &toY, &promoChar)
4342                || ParseOneMove(buf, moveNum - 1, &moveType,
4343                                 &fromX, &fromY, &toX, &toY, &promoChar);
4344           // end of long SAN patch
4345           if (valid) {
4346             (void) CoordsToAlgebraic(boards[moveNum - 1],
4347                                      PosFlags(moveNum - 1),
4348                                      fromY, fromX, toY, toX, promoChar,
4349                                      parseList[moveNum-1]);
4350             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4351               case MT_NONE:
4352               case MT_STALEMATE:
4353               default:
4354                 break;
4355               case MT_CHECK:
4356                 if(gameInfo.variant != VariantShogi)
4357                     strcat(parseList[moveNum - 1], "+");
4358                 break;
4359               case MT_CHECKMATE:
4360               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4361                 strcat(parseList[moveNum - 1], "#");
4362                 break;
4363             }
4364             strcat(parseList[moveNum - 1], " ");
4365             strcat(parseList[moveNum - 1], elapsed_time);
4366             /* currentMoveString is set as a side-effect of ParseOneMove */
4367             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4368             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4369             strcat(moveList[moveNum - 1], "\n");
4370
4371             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4372               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4373                 ChessSquare old, new = boards[moveNum][k][j];
4374                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4375                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4376                   if(old == new) continue;
4377                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4378                   else if(new == WhiteWazir || new == BlackWazir) {
4379                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4380                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4381                       else boards[moveNum][k][j] = old; // preserve type of Gold
4382                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4383                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4384               }
4385           } else {
4386             /* Move from ICS was illegal!?  Punt. */
4387             if (appData.debugMode) {
4388               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4389               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4390             }
4391             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4392             strcat(parseList[moveNum - 1], " ");
4393             strcat(parseList[moveNum - 1], elapsed_time);
4394             moveList[moveNum - 1][0] = NULLCHAR;
4395             fromX = fromY = toX = toY = -1;
4396           }
4397         }
4398   if (appData.debugMode) {
4399     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4400     setbuf(debugFP, NULL);
4401   }
4402
4403 #if ZIPPY
4404         /* Send move to chess program (BEFORE animating it). */
4405         if (appData.zippyPlay && !newGame && newMove &&
4406            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4407
4408             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4409                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4410                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4411                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4412                             move_str);
4413                     DisplayError(str, 0);
4414                 } else {
4415                     if (first.sendTime) {
4416                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4417                     }
4418                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4419                     if (firstMove && !bookHit) {
4420                         firstMove = FALSE;
4421                         if (first.useColors) {
4422                           SendToProgram(gameMode == IcsPlayingWhite ?
4423                                         "white\ngo\n" :
4424                                         "black\ngo\n", &first);
4425                         } else {
4426                           SendToProgram("go\n", &first);
4427                         }
4428                         first.maybeThinking = TRUE;
4429                     }
4430                 }
4431             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4432               if (moveList[moveNum - 1][0] == NULLCHAR) {
4433                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4434                 DisplayError(str, 0);
4435               } else {
4436                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4437                 SendMoveToProgram(moveNum - 1, &first);
4438               }
4439             }
4440         }
4441 #endif
4442     }
4443
4444     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4445         /* If move comes from a remote source, animate it.  If it
4446            isn't remote, it will have already been animated. */
4447         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4448             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4449         }
4450         if (!pausing && appData.highlightLastMove) {
4451             SetHighlights(fromX, fromY, toX, toY);
4452         }
4453     }
4454
4455     /* Start the clocks */
4456     whiteFlag = blackFlag = FALSE;
4457     appData.clockMode = !(basetime == 0 && increment == 0);
4458     if (ticking == 0) {
4459       ics_clock_paused = TRUE;
4460       StopClocks();
4461     } else if (ticking == 1) {
4462       ics_clock_paused = FALSE;
4463     }
4464     if (gameMode == IcsIdle ||
4465         relation == RELATION_OBSERVING_STATIC ||
4466         relation == RELATION_EXAMINING ||
4467         ics_clock_paused)
4468       DisplayBothClocks();
4469     else
4470       StartClocks();
4471
4472     /* Display opponents and material strengths */
4473     if (gameInfo.variant != VariantBughouse &&
4474         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4475         if (tinyLayout || smallLayout) {
4476             if(gameInfo.variant == VariantNormal)
4477               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4478                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4479                     basetime, increment);
4480             else
4481               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment, (int) gameInfo.variant);
4484         } else {
4485             if(gameInfo.variant == VariantNormal)
4486               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4487                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4488                     basetime, increment);
4489             else
4490               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment, VariantName(gameInfo.variant));
4493         }
4494         DisplayTitle(str);
4495   if (appData.debugMode) {
4496     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4497   }
4498     }
4499
4500
4501     /* Display the board */
4502     if (!pausing && !appData.noGUI) {
4503
4504       if (appData.premove)
4505           if (!gotPremove ||
4506              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4507              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4508               ClearPremoveHighlights();
4509
4510       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4511         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4512       DrawPosition(j, boards[currentMove]);
4513
4514       DisplayMove(moveNum - 1);
4515       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4516             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4517               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4518         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4519       }
4520     }
4521
4522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4523 #if ZIPPY
4524     if(bookHit) { // [HGM] book: simulate book reply
4525         static char bookMove[MSG_SIZ]; // a bit generous?
4526
4527         programStats.nodes = programStats.depth = programStats.time =
4528         programStats.score = programStats.got_only_move = 0;
4529         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4530
4531         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4532         strcat(bookMove, bookHit);
4533         HandleMachineMove(bookMove, &first);
4534     }
4535 #endif
4536 }
4537
4538 void
4539 GetMoveListEvent()
4540 {
4541     char buf[MSG_SIZ];
4542     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4543         ics_getting_history = H_REQUESTED;
4544         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4545         SendToICS(buf);
4546     }
4547 }
4548
4549 void
4550 AnalysisPeriodicEvent(force)
4551      int force;
4552 {
4553     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4554          && !force) || !appData.periodicUpdates)
4555       return;
4556
4557     /* Send . command to Crafty to collect stats */
4558     SendToProgram(".\n", &first);
4559
4560     /* Don't send another until we get a response (this makes
4561        us stop sending to old Crafty's which don't understand
4562        the "." command (sending illegal cmds resets node count & time,
4563        which looks bad)) */
4564     programStats.ok_to_send = 0;
4565 }
4566
4567 void ics_update_width(new_width)
4568         int new_width;
4569 {
4570         ics_printf("set width %d\n", new_width);
4571 }
4572
4573 void
4574 SendMoveToProgram(moveNum, cps)
4575      int moveNum;
4576      ChessProgramState *cps;
4577 {
4578     char buf[MSG_SIZ];
4579
4580     if (cps->useUsermove) {
4581       SendToProgram("usermove ", cps);
4582     }
4583     if (cps->useSAN) {
4584       char *space;
4585       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4586         int len = space - parseList[moveNum];
4587         memcpy(buf, parseList[moveNum], len);
4588         buf[len++] = '\n';
4589         buf[len] = NULLCHAR;
4590       } else {
4591         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4592       }
4593       SendToProgram(buf, cps);
4594     } else {
4595       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4596         AlphaRank(moveList[moveNum], 4);
4597         SendToProgram(moveList[moveNum], cps);
4598         AlphaRank(moveList[moveNum], 4); // and back
4599       } else
4600       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4601        * the engine. It would be nice to have a better way to identify castle
4602        * moves here. */
4603       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4604                                                                          && cps->useOOCastle) {
4605         int fromX = moveList[moveNum][0] - AAA;
4606         int fromY = moveList[moveNum][1] - ONE;
4607         int toX = moveList[moveNum][2] - AAA;
4608         int toY = moveList[moveNum][3] - ONE;
4609         if((boards[moveNum][fromY][fromX] == WhiteKing
4610             && boards[moveNum][toY][toX] == WhiteRook)
4611            || (boards[moveNum][fromY][fromX] == BlackKing
4612                && boards[moveNum][toY][toX] == BlackRook)) {
4613           if(toX > fromX) SendToProgram("O-O\n", cps);
4614           else SendToProgram("O-O-O\n", cps);
4615         }
4616         else SendToProgram(moveList[moveNum], cps);
4617       }
4618       else SendToProgram(moveList[moveNum], cps);
4619       /* End of additions by Tord */
4620     }
4621
4622     /* [HGM] setting up the opening has brought engine in force mode! */
4623     /*       Send 'go' if we are in a mode where machine should play. */
4624     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4625         (gameMode == TwoMachinesPlay   ||
4626 #if ZIPPY
4627          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4628 #endif
4629          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4630         SendToProgram("go\n", cps);
4631   if (appData.debugMode) {
4632     fprintf(debugFP, "(extra)\n");
4633   }
4634     }
4635     setboardSpoiledMachineBlack = 0;
4636 }
4637
4638 void
4639 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4640      ChessMove moveType;
4641      int fromX, fromY, toX, toY;
4642      char promoChar;
4643 {
4644     char user_move[MSG_SIZ];
4645
4646     switch (moveType) {
4647       default:
4648         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4649                 (int)moveType, fromX, fromY, toX, toY);
4650         DisplayError(user_move + strlen("say "), 0);
4651         break;
4652       case WhiteKingSideCastle:
4653       case BlackKingSideCastle:
4654       case WhiteQueenSideCastleWild:
4655       case BlackQueenSideCastleWild:
4656       /* PUSH Fabien */
4657       case WhiteHSideCastleFR:
4658       case BlackHSideCastleFR:
4659       /* POP Fabien */
4660         snprintf(user_move, MSG_SIZ, "o-o\n");
4661         break;
4662       case WhiteQueenSideCastle:
4663       case BlackQueenSideCastle:
4664       case WhiteKingSideCastleWild:
4665       case BlackKingSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteASideCastleFR:
4668       case BlackASideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4671         break;
4672       case WhiteNonPromotion:
4673       case BlackNonPromotion:
4674         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4675         break;
4676       case WhitePromotion:
4677       case BlackPromotion:
4678         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4679           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4681                 PieceToChar(WhiteFerz));
4682         else if(gameInfo.variant == VariantGreat)
4683           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4684                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685                 PieceToChar(WhiteMan));
4686         else
4687           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4688                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689                 promoChar);
4690         break;
4691       case WhiteDrop:
4692       case BlackDrop:
4693       drop:
4694         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4695                  ToUpper(PieceToChar((ChessSquare) fromX)),
4696                  AAA + toX, ONE + toY);
4697         break;
4698       case IllegalMove:  /* could be a variant we don't quite understand */
4699         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4700       case NormalMove:
4701       case WhiteCapturesEnPassant:
4702       case BlackCapturesEnPassant:
4703         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4704                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4705         break;
4706     }
4707     SendToICS(user_move);
4708     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4709         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4710 }
4711
4712 void
4713 UploadGameEvent()
4714 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4715     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4716     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4717     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4718         DisplayError("You cannot do this while you are playing or observing", 0);
4719         return;
4720     }
4721     if(gameMode != IcsExamining) { // is this ever not the case?
4722         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4723
4724         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4725           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4726         } else { // on FICS we must first go to general examine mode
4727           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4728         }
4729         if(gameInfo.variant != VariantNormal) {
4730             // try figure out wild number, as xboard names are not always valid on ICS
4731             for(i=1; i<=36; i++) {
4732               snprintf(buf, MSG_SIZ, "wild/%d", i);
4733                 if(StringToVariant(buf) == gameInfo.variant) break;
4734             }
4735             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4736             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4737             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4738         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4739         SendToICS(ics_prefix);
4740         SendToICS(buf);
4741         if(startedFromSetupPosition || backwardMostMove != 0) {
4742           fen = PositionToFEN(backwardMostMove, NULL);
4743           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4744             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4745             SendToICS(buf);
4746           } else { // FICS: everything has to set by separate bsetup commands
4747             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4748             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4749             SendToICS(buf);
4750             if(!WhiteOnMove(backwardMostMove)) {
4751                 SendToICS("bsetup tomove black\n");
4752             }
4753             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4754             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4755             SendToICS(buf);
4756             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4757             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4758             SendToICS(buf);
4759             i = boards[backwardMostMove][EP_STATUS];
4760             if(i >= 0) { // set e.p.
4761               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4762                 SendToICS(buf);
4763             }
4764             bsetup++;
4765           }
4766         }
4767       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4768             SendToICS("bsetup done\n"); // switch to normal examining.
4769     }
4770     for(i = backwardMostMove; i<last; i++) {
4771         char buf[20];
4772         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4773         SendToICS(buf);
4774     }
4775     SendToICS(ics_prefix);
4776     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4777 }
4778
4779 void
4780 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4781      int rf, ff, rt, ft;
4782      char promoChar;
4783      char move[7];
4784 {
4785     if (rf == DROP_RANK) {
4786       sprintf(move, "%c@%c%c\n",
4787                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4788     } else {
4789         if (promoChar == 'x' || promoChar == NULLCHAR) {
4790           sprintf(move, "%c%c%c%c\n",
4791                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4792         } else {
4793             sprintf(move, "%c%c%c%c%c\n",
4794                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4795         }
4796     }
4797 }
4798
4799 void
4800 ProcessICSInitScript(f)
4801      FILE *f;
4802 {
4803     char buf[MSG_SIZ];
4804
4805     while (fgets(buf, MSG_SIZ, f)) {
4806         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4807     }
4808
4809     fclose(f);
4810 }
4811
4812
4813 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4814 void
4815 AlphaRank(char *move, int n)
4816 {
4817 //    char *p = move, c; int x, y;
4818
4819     if (appData.debugMode) {
4820         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4821     }
4822
4823     if(move[1]=='*' &&
4824        move[2]>='0' && move[2]<='9' &&
4825        move[3]>='a' && move[3]<='x'    ) {
4826         move[1] = '@';
4827         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4828         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4829     } else
4830     if(move[0]>='0' && move[0]<='9' &&
4831        move[1]>='a' && move[1]<='x' &&
4832        move[2]>='0' && move[2]<='9' &&
4833        move[3]>='a' && move[3]<='x'    ) {
4834         /* input move, Shogi -> normal */
4835         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4836         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4837         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4838         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4839     } else
4840     if(move[1]=='@' &&
4841        move[3]>='0' && move[3]<='9' &&
4842        move[2]>='a' && move[2]<='x'    ) {
4843         move[1] = '*';
4844         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4845         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4846     } else
4847     if(
4848        move[0]>='a' && move[0]<='x' &&
4849        move[3]>='0' && move[3]<='9' &&
4850        move[2]>='a' && move[2]<='x'    ) {
4851          /* output move, normal -> Shogi */
4852         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4853         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4854         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4855         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4856         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4857     }
4858     if (appData.debugMode) {
4859         fprintf(debugFP, "   out = '%s'\n", move);
4860     }
4861 }
4862
4863 char yy_textstr[8000];
4864
4865 /* Parser for moves from gnuchess, ICS, or user typein box */
4866 Boolean
4867 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4868      char *move;
4869      int moveNum;
4870      ChessMove *moveType;
4871      int *fromX, *fromY, *toX, *toY;
4872      char *promoChar;
4873 {
4874     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4875
4876     switch (*moveType) {
4877       case WhitePromotion:
4878       case BlackPromotion:
4879       case WhiteNonPromotion:
4880       case BlackNonPromotion:
4881       case NormalMove:
4882       case WhiteCapturesEnPassant:
4883       case BlackCapturesEnPassant:
4884       case WhiteKingSideCastle:
4885       case WhiteQueenSideCastle:
4886       case BlackKingSideCastle:
4887       case BlackQueenSideCastle:
4888       case WhiteKingSideCastleWild:
4889       case WhiteQueenSideCastleWild:
4890       case BlackKingSideCastleWild:
4891       case BlackQueenSideCastleWild:
4892       /* Code added by Tord: */
4893       case WhiteHSideCastleFR:
4894       case WhiteASideCastleFR:
4895       case BlackHSideCastleFR:
4896       case BlackASideCastleFR:
4897       /* End of code added by Tord */
4898       case IllegalMove:         /* bug or odd chess variant */
4899         *fromX = currentMoveString[0] - AAA;
4900         *fromY = currentMoveString[1] - ONE;
4901         *toX = currentMoveString[2] - AAA;
4902         *toY = currentMoveString[3] - ONE;
4903         *promoChar = currentMoveString[4];
4904         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4905             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4906     if (appData.debugMode) {
4907         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4908     }
4909             *fromX = *fromY = *toX = *toY = 0;
4910             return FALSE;
4911         }
4912         if (appData.testLegality) {
4913           return (*moveType != IllegalMove);
4914         } else {
4915           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4916                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4917         }
4918
4919       case WhiteDrop:
4920       case BlackDrop:
4921         *fromX = *moveType == WhiteDrop ?
4922           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4923           (int) CharToPiece(ToLower(currentMoveString[0]));
4924         *fromY = DROP_RANK;
4925         *toX = currentMoveString[2] - AAA;
4926         *toY = currentMoveString[3] - ONE;
4927         *promoChar = NULLCHAR;
4928         return TRUE;
4929
4930       case AmbiguousMove:
4931       case ImpossibleMove:
4932       case EndOfFile:
4933       case ElapsedTime:
4934       case Comment:
4935       case PGNTag:
4936       case NAG:
4937       case WhiteWins:
4938       case BlackWins:
4939       case GameIsDrawn:
4940       default:
4941     if (appData.debugMode) {
4942         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4943     }
4944         /* bug? */
4945         *fromX = *fromY = *toX = *toY = 0;
4946         *promoChar = NULLCHAR;
4947         return FALSE;
4948     }
4949 }
4950
4951
4952 void
4953 ParsePV(char *pv, Boolean storeComments)
4954 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4955   int fromX, fromY, toX, toY; char promoChar;
4956   ChessMove moveType;
4957   Boolean valid;
4958   int nr = 0;
4959
4960   endPV = forwardMostMove;
4961   do {
4962     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4963     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4964     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4965 if(appData.debugMode){
4966 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);
4967 }
4968     if(!valid && nr == 0 &&
4969        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4970         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4971         // Hande case where played move is different from leading PV move
4972         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4973         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4974         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4975         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4976           endPV += 2; // if position different, keep this
4977           moveList[endPV-1][0] = fromX + AAA;
4978           moveList[endPV-1][1] = fromY + ONE;
4979           moveList[endPV-1][2] = toX + AAA;
4980           moveList[endPV-1][3] = toY + ONE;
4981           parseList[endPV-1][0] = NULLCHAR;
4982           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4983         }
4984       }
4985     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4986     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4987     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4988     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4989         valid++; // allow comments in PV
4990         continue;
4991     }
4992     nr++;
4993     if(endPV+1 > framePtr) break; // no space, truncate
4994     if(!valid) break;
4995     endPV++;
4996     CopyBoard(boards[endPV], boards[endPV-1]);
4997     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4998     moveList[endPV-1][0] = fromX + AAA;
4999     moveList[endPV-1][1] = fromY + ONE;
5000     moveList[endPV-1][2] = toX + AAA;
5001     moveList[endPV-1][3] = toY + ONE;
5002     moveList[endPV-1][4] = promoChar;
5003     moveList[endPV-1][5] = NULLCHAR;
5004     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5005     if(storeComments)
5006         CoordsToAlgebraic(boards[endPV - 1],
5007                              PosFlags(endPV - 1),
5008                              fromY, fromX, toY, toX, promoChar,
5009                              parseList[endPV - 1]);
5010     else
5011         parseList[endPV-1][0] = NULLCHAR;
5012   } while(valid);
5013   currentMove = endPV;
5014   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5015   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5016                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5017   DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 static int lastX, lastY;
5021
5022 Boolean
5023 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5024 {
5025         int startPV;
5026         char *p;
5027
5028         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5029         lastX = x; lastY = y;
5030         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5031         startPV = index;
5032         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5033         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5034         index = startPV;
5035         do{ while(buf[index] && buf[index] != '\n') index++;
5036         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5037         buf[index] = 0;
5038         ParsePV(buf+startPV, FALSE);
5039         *start = startPV; *end = index-1;
5040         return TRUE;
5041 }
5042
5043 Boolean
5044 LoadPV(int x, int y)
5045 { // called on right mouse click to load PV
5046   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5047   lastX = x; lastY = y;
5048   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5049   return TRUE;
5050 }
5051
5052 void
5053 UnLoadPV()
5054 {
5055   if(endPV < 0) return;
5056   endPV = -1;
5057   currentMove = forwardMostMove;
5058   ClearPremoveHighlights();
5059   DrawPosition(TRUE, boards[currentMove]);
5060 }
5061
5062 void
5063 MovePV(int x, int y, int h)
5064 { // step through PV based on mouse coordinates (called on mouse move)
5065   int margin = h>>3, step = 0;
5066
5067   if(endPV < 0) return;
5068   // we must somehow check if right button is still down (might be released off board!)
5069   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5070   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5071   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5072   if(!step) return;
5073   lastX = x; lastY = y;
5074   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5075   currentMove += step;
5076   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5077   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5078                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5079   DrawPosition(FALSE, boards[currentMove]);
5080 }
5081
5082
5083 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5084 // All positions will have equal probability, but the current method will not provide a unique
5085 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5086 #define DARK 1
5087 #define LITE 2
5088 #define ANY 3
5089
5090 int squaresLeft[4];
5091 int piecesLeft[(int)BlackPawn];
5092 int seed, nrOfShuffles;
5093
5094 void GetPositionNumber()
5095 {       // sets global variable seed
5096         int i;
5097
5098         seed = appData.defaultFrcPosition;
5099         if(seed < 0) { // randomize based on time for negative FRC position numbers
5100                 for(i=0; i<50; i++) seed += random();
5101                 seed = random() ^ random() >> 8 ^ random() << 8;
5102                 if(seed<0) seed = -seed;
5103         }
5104 }
5105
5106 int put(Board board, int pieceType, int rank, int n, int shade)
5107 // put the piece on the (n-1)-th empty squares of the given shade
5108 {
5109         int i;
5110
5111         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5112                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5113                         board[rank][i] = (ChessSquare) pieceType;
5114                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5115                         squaresLeft[ANY]--;
5116                         piecesLeft[pieceType]--;
5117                         return i;
5118                 }
5119         }
5120         return -1;
5121 }
5122
5123
5124 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5125 // calculate where the next piece goes, (any empty square), and put it there
5126 {
5127         int i;
5128
5129         i = seed % squaresLeft[shade];
5130         nrOfShuffles *= squaresLeft[shade];
5131         seed /= squaresLeft[shade];
5132         put(board, pieceType, rank, i, shade);
5133 }
5134
5135 void AddTwoPieces(Board board, int pieceType, int rank)
5136 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5137 {
5138         int i, n=squaresLeft[ANY], j=n-1, k;
5139
5140         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5141         i = seed % k;  // pick one
5142         nrOfShuffles *= k;
5143         seed /= k;
5144         while(i >= j) i -= j--;
5145         j = n - 1 - j; i += j;
5146         put(board, pieceType, rank, j, ANY);
5147         put(board, pieceType, rank, i, ANY);
5148 }
5149
5150 void SetUpShuffle(Board board, int number)
5151 {
5152         int i, p, first=1;
5153
5154         GetPositionNumber(); nrOfShuffles = 1;
5155
5156         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5157         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5158         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5159
5160         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5161
5162         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5163             p = (int) board[0][i];
5164             if(p < (int) BlackPawn) piecesLeft[p] ++;
5165             board[0][i] = EmptySquare;
5166         }
5167
5168         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5169             // shuffles restricted to allow normal castling put KRR first
5170             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5171                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5172             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5173                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5174             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5175                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5176             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5177                 put(board, WhiteRook, 0, 0, ANY);
5178             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5179         }
5180
5181         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5182             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5183             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5184                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5185                 while(piecesLeft[p] >= 2) {
5186                     AddOnePiece(board, p, 0, LITE);
5187                     AddOnePiece(board, p, 0, DARK);
5188                 }
5189                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5190             }
5191
5192         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5193             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5194             // but we leave King and Rooks for last, to possibly obey FRC restriction
5195             if(p == (int)WhiteRook) continue;
5196             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5197             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5198         }
5199
5200         // now everything is placed, except perhaps King (Unicorn) and Rooks
5201
5202         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5203             // Last King gets castling rights
5204             while(piecesLeft[(int)WhiteUnicorn]) {
5205                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5206                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5207             }
5208
5209             while(piecesLeft[(int)WhiteKing]) {
5210                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5211                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5212             }
5213
5214
5215         } else {
5216             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5217             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5218         }
5219
5220         // Only Rooks can be left; simply place them all
5221         while(piecesLeft[(int)WhiteRook]) {
5222                 i = put(board, WhiteRook, 0, 0, ANY);
5223                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5224                         if(first) {
5225                                 first=0;
5226                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5227                         }
5228                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5229                 }
5230         }
5231         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5232             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5233         }
5234
5235         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5236 }
5237
5238 int SetCharTable( char *table, const char * map )
5239 /* [HGM] moved here from winboard.c because of its general usefulness */
5240 /*       Basically a safe strcpy that uses the last character as King */
5241 {
5242     int result = FALSE; int NrPieces;
5243
5244     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5245                     && NrPieces >= 12 && !(NrPieces&1)) {
5246         int i; /* [HGM] Accept even length from 12 to 34 */
5247
5248         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5249         for( i=0; i<NrPieces/2-1; i++ ) {
5250             table[i] = map[i];
5251             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5252         }
5253         table[(int) WhiteKing]  = map[NrPieces/2-1];
5254         table[(int) BlackKing]  = map[NrPieces-1];
5255
5256         result = TRUE;
5257     }
5258
5259     return result;
5260 }
5261
5262 void Prelude(Board board)
5263 {       // [HGM] superchess: random selection of exo-pieces
5264         int i, j, k; ChessSquare p;
5265         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5266
5267         GetPositionNumber(); // use FRC position number
5268
5269         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5270             SetCharTable(pieceToChar, appData.pieceToCharTable);
5271             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5272                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5273         }
5274
5275         j = seed%4;                 seed /= 4;
5276         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5277         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5278         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5279         j = seed%3 + (seed%3 >= j); seed /= 3;
5280         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5281         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5282         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5283         j = seed%3;                 seed /= 3;
5284         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5285         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5286         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5287         j = seed%2 + (seed%2 >= j); seed /= 2;
5288         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5289         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5290         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5291         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5292         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5293         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5294         put(board, exoPieces[0],    0, 0, ANY);
5295         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5296 }
5297
5298 void
5299 InitPosition(redraw)
5300      int redraw;
5301 {
5302     ChessSquare (* pieces)[BOARD_FILES];
5303     int i, j, pawnRow, overrule,
5304     oldx = gameInfo.boardWidth,
5305     oldy = gameInfo.boardHeight,
5306     oldh = gameInfo.holdingsWidth;
5307     static int oldv;
5308
5309     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5310
5311     /* [AS] Initialize pv info list [HGM] and game status */
5312     {
5313         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5314             pvInfoList[i].depth = 0;
5315             boards[i][EP_STATUS] = EP_NONE;
5316             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5317         }
5318
5319         initialRulePlies = 0; /* 50-move counter start */
5320
5321         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5322         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5323     }
5324
5325
5326     /* [HGM] logic here is completely changed. In stead of full positions */
5327     /* the initialized data only consist of the two backranks. The switch */
5328     /* selects which one we will use, which is than copied to the Board   */
5329     /* initialPosition, which for the rest is initialized by Pawns and    */
5330     /* empty squares. This initial position is then copied to boards[0],  */
5331     /* possibly after shuffling, so that it remains available.            */
5332
5333     gameInfo.holdingsWidth = 0; /* default board sizes */
5334     gameInfo.boardWidth    = 8;
5335     gameInfo.boardHeight   = 8;
5336     gameInfo.holdingsSize  = 0;
5337     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5338     for(i=0; i<BOARD_FILES-2; i++)
5339       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5340     initialPosition[EP_STATUS] = EP_NONE;
5341     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5342     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5343          SetCharTable(pieceNickName, appData.pieceNickNames);
5344     else SetCharTable(pieceNickName, "............");
5345     pieces = FIDEArray;
5346
5347     switch (gameInfo.variant) {
5348     case VariantFischeRandom:
5349       shuffleOpenings = TRUE;
5350     default:
5351       break;
5352     case VariantShatranj:
5353       pieces = ShatranjArray;
5354       nrCastlingRights = 0;
5355       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5356       break;
5357     case VariantMakruk:
5358       pieces = makrukArray;
5359       nrCastlingRights = 0;
5360       startedFromSetupPosition = TRUE;
5361       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5362       break;
5363     case VariantTwoKings:
5364       pieces = twoKingsArray;
5365       break;
5366     case VariantCapaRandom:
5367       shuffleOpenings = TRUE;
5368     case VariantCapablanca:
5369       pieces = CapablancaArray;
5370       gameInfo.boardWidth = 10;
5371       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5372       break;
5373     case VariantGothic:
5374       pieces = GothicArray;
5375       gameInfo.boardWidth = 10;
5376       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5377       break;
5378     case VariantSChess:
5379       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5380       gameInfo.holdingsSize = 7;
5381       break;
5382     case VariantJanus:
5383       pieces = JanusArray;
5384       gameInfo.boardWidth = 10;
5385       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5386       nrCastlingRights = 6;
5387         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5388         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5389         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5390         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5391         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5392         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5393       break;
5394     case VariantFalcon:
5395       pieces = FalconArray;
5396       gameInfo.boardWidth = 10;
5397       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5398       break;
5399     case VariantXiangqi:
5400       pieces = XiangqiArray;
5401       gameInfo.boardWidth  = 9;
5402       gameInfo.boardHeight = 10;
5403       nrCastlingRights = 0;
5404       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5405       break;
5406     case VariantShogi:
5407       pieces = ShogiArray;
5408       gameInfo.boardWidth  = 9;
5409       gameInfo.boardHeight = 9;
5410       gameInfo.holdingsSize = 7;
5411       nrCastlingRights = 0;
5412       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5413       break;
5414     case VariantCourier:
5415       pieces = CourierArray;
5416       gameInfo.boardWidth  = 12;
5417       nrCastlingRights = 0;
5418       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5419       break;
5420     case VariantKnightmate:
5421       pieces = KnightmateArray;
5422       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5423       break;
5424     case VariantSpartan:
5425       pieces = SpartanArray;
5426       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5427       break;
5428     case VariantFairy:
5429       pieces = fairyArray;
5430       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5431       break;
5432     case VariantGreat:
5433       pieces = GreatArray;
5434       gameInfo.boardWidth = 10;
5435       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5436       gameInfo.holdingsSize = 8;
5437       break;
5438     case VariantSuper:
5439       pieces = FIDEArray;
5440       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5441       gameInfo.holdingsSize = 8;
5442       startedFromSetupPosition = TRUE;
5443       break;
5444     case VariantCrazyhouse:
5445     case VariantBughouse:
5446       pieces = FIDEArray;
5447       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5448       gameInfo.holdingsSize = 5;
5449       break;
5450     case VariantWildCastle:
5451       pieces = FIDEArray;
5452       /* !!?shuffle with kings guaranteed to be on d or e file */
5453       shuffleOpenings = 1;
5454       break;
5455     case VariantNoCastle:
5456       pieces = FIDEArray;
5457       nrCastlingRights = 0;
5458       /* !!?unconstrained back-rank shuffle */
5459       shuffleOpenings = 1;
5460       break;
5461     }
5462
5463     overrule = 0;
5464     if(appData.NrFiles >= 0) {
5465         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5466         gameInfo.boardWidth = appData.NrFiles;
5467     }
5468     if(appData.NrRanks >= 0) {
5469         gameInfo.boardHeight = appData.NrRanks;
5470     }
5471     if(appData.holdingsSize >= 0) {
5472         i = appData.holdingsSize;
5473         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5474         gameInfo.holdingsSize = i;
5475     }
5476     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5477     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5478         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5479
5480     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5481     if(pawnRow < 1) pawnRow = 1;
5482     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5483
5484     /* User pieceToChar list overrules defaults */
5485     if(appData.pieceToCharTable != NULL)
5486         SetCharTable(pieceToChar, appData.pieceToCharTable);
5487
5488     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5489
5490         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5491             s = (ChessSquare) 0; /* account holding counts in guard band */
5492         for( i=0; i<BOARD_HEIGHT; i++ )
5493             initialPosition[i][j] = s;
5494
5495         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5496         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5497         initialPosition[pawnRow][j] = WhitePawn;
5498         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5499         if(gameInfo.variant == VariantXiangqi) {
5500             if(j&1) {
5501                 initialPosition[pawnRow][j] =
5502                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5503                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5504                    initialPosition[2][j] = WhiteCannon;
5505                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5506                 }
5507             }
5508         }
5509         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5510     }
5511     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5512
5513             j=BOARD_LEFT+1;
5514             initialPosition[1][j] = WhiteBishop;
5515             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5516             j=BOARD_RGHT-2;
5517             initialPosition[1][j] = WhiteRook;
5518             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5519     }
5520
5521     if( nrCastlingRights == -1) {
5522         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5523         /*       This sets default castling rights from none to normal corners   */
5524         /* Variants with other castling rights must set them themselves above    */
5525         nrCastlingRights = 6;
5526
5527         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5528         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5529         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5530         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5531         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5532         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5533      }
5534
5535      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5536      if(gameInfo.variant == VariantGreat) { // promotion commoners
5537         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5538         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5539         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5540         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5541      }
5542      if( gameInfo.variant == VariantSChess ) {
5543       initialPosition[1][0] = BlackMarshall;
5544       initialPosition[2][0] = BlackAngel;
5545       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5546       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5547       initialPosition[1][1] = initialPosition[2][1] = 
5548       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5549      }
5550   if (appData.debugMode) {
5551     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5552   }
5553     if(shuffleOpenings) {
5554         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5555         startedFromSetupPosition = TRUE;
5556     }
5557     if(startedFromPositionFile) {
5558       /* [HGM] loadPos: use PositionFile for every new game */
5559       CopyBoard(initialPosition, filePosition);
5560       for(i=0; i<nrCastlingRights; i++)
5561           initialRights[i] = filePosition[CASTLING][i];
5562       startedFromSetupPosition = TRUE;
5563     }
5564
5565     CopyBoard(boards[0], initialPosition);
5566
5567     if(oldx != gameInfo.boardWidth ||
5568        oldy != gameInfo.boardHeight ||
5569        oldv != gameInfo.variant ||
5570        oldh != gameInfo.holdingsWidth
5571                                          )
5572             InitDrawingSizes(-2 ,0);
5573
5574     oldv = gameInfo.variant;
5575     if (redraw)
5576       DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 SendBoard(cps, moveNum)
5581      ChessProgramState *cps;
5582      int moveNum;
5583 {
5584     char message[MSG_SIZ];
5585
5586     if (cps->useSetboard) {
5587       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5588       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5589       SendToProgram(message, cps);
5590       free(fen);
5591
5592     } else {
5593       ChessSquare *bp;
5594       int i, j;
5595       /* Kludge to set black to move, avoiding the troublesome and now
5596        * deprecated "black" command.
5597        */
5598       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5599         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5600
5601       SendToProgram("edit\n", cps);
5602       SendToProgram("#\n", cps);
5603       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5604         bp = &boards[moveNum][i][BOARD_LEFT];
5605         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5606           if ((int) *bp < (int) BlackPawn) {
5607             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5608                     AAA + j, ONE + i);
5609             if(message[0] == '+' || message[0] == '~') {
5610               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5611                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5612                         AAA + j, ONE + i);
5613             }
5614             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5615                 message[1] = BOARD_RGHT   - 1 - j + '1';
5616                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5617             }
5618             SendToProgram(message, cps);
5619           }
5620         }
5621       }
5622
5623       SendToProgram("c\n", cps);
5624       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5625         bp = &boards[moveNum][i][BOARD_LEFT];
5626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5627           if (((int) *bp != (int) EmptySquare)
5628               && ((int) *bp >= (int) BlackPawn)) {
5629             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5630                     AAA + j, ONE + i);
5631             if(message[0] == '+' || message[0] == '~') {
5632               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5633                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5634                         AAA + j, ONE + i);
5635             }
5636             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5637                 message[1] = BOARD_RGHT   - 1 - j + '1';
5638                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5639             }
5640             SendToProgram(message, cps);
5641           }
5642         }
5643       }
5644
5645       SendToProgram(".\n", cps);
5646     }
5647     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5648 }
5649
5650 static int autoQueen; // [HGM] oneclick
5651
5652 int
5653 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5654 {
5655     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5656     /* [HGM] add Shogi promotions */
5657     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5658     ChessSquare piece;
5659     ChessMove moveType;
5660     Boolean premove;
5661
5662     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5663     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5664
5665     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5666       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5667         return FALSE;
5668
5669     piece = boards[currentMove][fromY][fromX];
5670     if(gameInfo.variant == VariantShogi) {
5671         promotionZoneSize = BOARD_HEIGHT/3;
5672         highestPromotingPiece = (int)WhiteFerz;
5673     } else if(gameInfo.variant == VariantMakruk) {
5674         promotionZoneSize = 3;
5675     }
5676
5677     // Treat Lance as Pawn when it is not representing Amazon
5678     if(gameInfo.variant != VariantSuper) {
5679         if(piece == WhiteLance) piece = WhitePawn; else
5680         if(piece == BlackLance) piece = BlackPawn;
5681     }
5682
5683     // next weed out all moves that do not touch the promotion zone at all
5684     if((int)piece >= BlackPawn) {
5685         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5686              return FALSE;
5687         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5688     } else {
5689         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5690            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5691     }
5692
5693     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5694
5695     // weed out mandatory Shogi promotions
5696     if(gameInfo.variant == VariantShogi) {
5697         if(piece >= BlackPawn) {
5698             if(toY == 0 && piece == BlackPawn ||
5699                toY == 0 && piece == BlackQueen ||
5700                toY <= 1 && piece == BlackKnight) {
5701                 *promoChoice = '+';
5702                 return FALSE;
5703             }
5704         } else {
5705             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5706                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5707                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5708                 *promoChoice = '+';
5709                 return FALSE;
5710             }
5711         }
5712     }
5713
5714     // weed out obviously illegal Pawn moves
5715     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5716         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5717         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5718         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5719         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5720         // note we are not allowed to test for valid (non-)capture, due to premove
5721     }
5722
5723     // we either have a choice what to promote to, or (in Shogi) whether to promote
5724     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5725         *promoChoice = PieceToChar(BlackFerz);  // no choice
5726         return FALSE;
5727     }
5728     // no sense asking what we must promote to if it is going to explode...
5729     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5730         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5731         return FALSE;
5732     }
5733     if(autoQueen) { // predetermined
5734         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5735              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5736         else *promoChoice = PieceToChar(BlackQueen);
5737         return FALSE;
5738     }
5739
5740     // suppress promotion popup on illegal moves that are not premoves
5741     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5742               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5743     if(appData.testLegality && !premove) {
5744         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5745                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5746         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5747             return FALSE;
5748     }
5749
5750     return TRUE;
5751 }
5752
5753 int
5754 InPalace(row, column)
5755      int row, column;
5756 {   /* [HGM] for Xiangqi */
5757     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5758          column < (BOARD_WIDTH + 4)/2 &&
5759          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5760     return FALSE;
5761 }
5762
5763 int
5764 PieceForSquare (x, y)
5765      int x;
5766      int y;
5767 {
5768   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5769      return -1;
5770   else
5771      return boards[currentMove][y][x];
5772 }
5773
5774 int
5775 OKToStartUserMove(x, y)
5776      int x, y;
5777 {
5778     ChessSquare from_piece;
5779     int white_piece;
5780
5781     if (matchMode) return FALSE;
5782     if (gameMode == EditPosition) return TRUE;
5783
5784     if (x >= 0 && y >= 0)
5785       from_piece = boards[currentMove][y][x];
5786     else
5787       from_piece = EmptySquare;
5788
5789     if (from_piece == EmptySquare) return FALSE;
5790
5791     white_piece = (int)from_piece >= (int)WhitePawn &&
5792       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5793
5794     switch (gameMode) {
5795       case PlayFromGameFile:
5796       case AnalyzeFile:
5797       case TwoMachinesPlay:
5798       case EndOfGame:
5799         return FALSE;
5800
5801       case IcsObserving:
5802       case IcsIdle:
5803         return FALSE;
5804
5805       case MachinePlaysWhite:
5806       case IcsPlayingBlack:
5807         if (appData.zippyPlay) return FALSE;
5808         if (white_piece) {
5809             DisplayMoveError(_("You are playing Black"));
5810             return FALSE;
5811         }
5812         break;
5813
5814       case MachinePlaysBlack:
5815       case IcsPlayingWhite:
5816         if (appData.zippyPlay) return FALSE;
5817         if (!white_piece) {
5818             DisplayMoveError(_("You are playing White"));
5819             return FALSE;
5820         }
5821         break;
5822
5823       case EditGame:
5824         if (!white_piece && WhiteOnMove(currentMove)) {
5825             DisplayMoveError(_("It is White's turn"));
5826             return FALSE;
5827         }
5828         if (white_piece && !WhiteOnMove(currentMove)) {
5829             DisplayMoveError(_("It is Black's turn"));
5830             return FALSE;
5831         }
5832         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5833             /* Editing correspondence game history */
5834             /* Could disallow this or prompt for confirmation */
5835             cmailOldMove = -1;
5836         }
5837         break;
5838
5839       case BeginningOfGame:
5840         if (appData.icsActive) return FALSE;
5841         if (!appData.noChessProgram) {
5842             if (!white_piece) {
5843                 DisplayMoveError(_("You are playing White"));
5844                 return FALSE;
5845             }
5846         }
5847         break;
5848
5849       case Training:
5850         if (!white_piece && WhiteOnMove(currentMove)) {
5851             DisplayMoveError(_("It is White's turn"));
5852             return FALSE;
5853         }
5854         if (white_piece && !WhiteOnMove(currentMove)) {
5855             DisplayMoveError(_("It is Black's turn"));
5856             return FALSE;
5857         }
5858         break;
5859
5860       default:
5861       case IcsExamining:
5862         break;
5863     }
5864     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5865         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5866         && gameMode != AnalyzeFile && gameMode != Training) {
5867         DisplayMoveError(_("Displayed position is not current"));
5868         return FALSE;
5869     }
5870     return TRUE;
5871 }
5872
5873 Boolean
5874 OnlyMove(int *x, int *y, Boolean captures) {
5875     DisambiguateClosure cl;
5876     if (appData.zippyPlay) return FALSE;
5877     switch(gameMode) {
5878       case MachinePlaysBlack:
5879       case IcsPlayingWhite:
5880       case BeginningOfGame:
5881         if(!WhiteOnMove(currentMove)) return FALSE;
5882         break;
5883       case MachinePlaysWhite:
5884       case IcsPlayingBlack:
5885         if(WhiteOnMove(currentMove)) return FALSE;
5886         break;
5887       case EditGame:
5888         break;
5889       default:
5890         return FALSE;
5891     }
5892     cl.pieceIn = EmptySquare;
5893     cl.rfIn = *y;
5894     cl.ffIn = *x;
5895     cl.rtIn = -1;
5896     cl.ftIn = -1;
5897     cl.promoCharIn = NULLCHAR;
5898     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5899     if( cl.kind == NormalMove ||
5900         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5901         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5902         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5903       fromX = cl.ff;
5904       fromY = cl.rf;
5905       *x = cl.ft;
5906       *y = cl.rt;
5907       return TRUE;
5908     }
5909     if(cl.kind != ImpossibleMove) return FALSE;
5910     cl.pieceIn = EmptySquare;
5911     cl.rfIn = -1;
5912     cl.ffIn = -1;
5913     cl.rtIn = *y;
5914     cl.ftIn = *x;
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       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5926       return TRUE;
5927     }
5928     return FALSE;
5929 }
5930
5931 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5932 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5933 int lastLoadGameUseList = FALSE;
5934 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5935 ChessMove lastLoadGameStart = EndOfFile;
5936
5937 void
5938 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5939      int fromX, fromY, toX, toY;
5940      int promoChar;
5941 {
5942     ChessMove moveType;
5943     ChessSquare pdown, pup;
5944
5945     /* Check if the user is playing in turn.  This is complicated because we
5946        let the user "pick up" a piece before it is his turn.  So the piece he
5947        tried to pick up may have been captured by the time he puts it down!
5948        Therefore we use the color the user is supposed to be playing in this
5949        test, not the color of the piece that is currently on the starting
5950        square---except in EditGame mode, where the user is playing both
5951        sides; fortunately there the capture race can't happen.  (It can
5952        now happen in IcsExamining mode, but that's just too bad.  The user
5953        will get a somewhat confusing message in that case.)
5954        */
5955
5956     switch (gameMode) {
5957       case PlayFromGameFile:
5958       case AnalyzeFile:
5959       case TwoMachinesPlay:
5960       case EndOfGame:
5961       case IcsObserving:
5962       case IcsIdle:
5963         /* We switched into a game mode where moves are not accepted,
5964            perhaps while the mouse button was down. */
5965         return;
5966
5967       case MachinePlaysWhite:
5968         /* User is moving for Black */
5969         if (WhiteOnMove(currentMove)) {
5970             DisplayMoveError(_("It is White's turn"));
5971             return;
5972         }
5973         break;
5974
5975       case MachinePlaysBlack:
5976         /* User is moving for White */
5977         if (!WhiteOnMove(currentMove)) {
5978             DisplayMoveError(_("It is Black's turn"));
5979             return;
5980         }
5981         break;
5982
5983       case EditGame:
5984       case IcsExamining:
5985       case BeginningOfGame:
5986       case AnalyzeMode:
5987       case Training:
5988         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
5989         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5990             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5991             /* User is moving for Black */
5992             if (WhiteOnMove(currentMove)) {
5993                 DisplayMoveError(_("It is White's turn"));
5994                 return;
5995             }
5996         } else {
5997             /* User is moving for White */
5998             if (!WhiteOnMove(currentMove)) {
5999                 DisplayMoveError(_("It is Black's turn"));
6000                 return;
6001             }
6002         }
6003         break;
6004
6005       case IcsPlayingBlack:
6006         /* User is moving for Black */
6007         if (WhiteOnMove(currentMove)) {
6008             if (!appData.premove) {
6009                 DisplayMoveError(_("It is White's turn"));
6010             } else if (toX >= 0 && toY >= 0) {
6011                 premoveToX = toX;
6012                 premoveToY = toY;
6013                 premoveFromX = fromX;
6014                 premoveFromY = fromY;
6015                 premovePromoChar = promoChar;
6016                 gotPremove = 1;
6017                 if (appData.debugMode)
6018                     fprintf(debugFP, "Got premove: fromX %d,"
6019                             "fromY %d, toX %d, toY %d\n",
6020                             fromX, fromY, toX, toY);
6021             }
6022             return;
6023         }
6024         break;
6025
6026       case IcsPlayingWhite:
6027         /* User is moving for White */
6028         if (!WhiteOnMove(currentMove)) {
6029             if (!appData.premove) {
6030                 DisplayMoveError(_("It is Black's turn"));
6031             } else if (toX >= 0 && toY >= 0) {
6032                 premoveToX = toX;
6033                 premoveToY = toY;
6034                 premoveFromX = fromX;
6035                 premoveFromY = fromY;
6036                 premovePromoChar = promoChar;
6037                 gotPremove = 1;
6038                 if (appData.debugMode)
6039                     fprintf(debugFP, "Got premove: fromX %d,"
6040                             "fromY %d, toX %d, toY %d\n",
6041                             fromX, fromY, toX, toY);
6042             }
6043             return;
6044         }
6045         break;
6046
6047       default:
6048         break;
6049
6050       case EditPosition:
6051         /* EditPosition, empty square, or different color piece;
6052            click-click move is possible */
6053         if (toX == -2 || toY == -2) {
6054             boards[0][fromY][fromX] = EmptySquare;
6055             DrawPosition(FALSE, boards[currentMove]);
6056             return;
6057         } else if (toX >= 0 && toY >= 0) {
6058             boards[0][toY][toX] = boards[0][fromY][fromX];
6059             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6060                 if(boards[0][fromY][0] != EmptySquare) {
6061                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6062                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6063                 }
6064             } else
6065             if(fromX == BOARD_RGHT+1) {
6066                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6067                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6068                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6069                 }
6070             } else
6071             boards[0][fromY][fromX] = EmptySquare;
6072             DrawPosition(FALSE, boards[currentMove]);
6073             return;
6074         }
6075         return;
6076     }
6077
6078     if(toX < 0 || toY < 0) return;
6079     pdown = boards[currentMove][fromY][fromX];
6080     pup = boards[currentMove][toY][toX];
6081
6082     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6083     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6084          if( pup != EmptySquare ) return;
6085          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6086            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6087                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6088            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6089            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6090            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6091            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6092          fromY = DROP_RANK;
6093     }
6094
6095     /* [HGM] always test for legality, to get promotion info */
6096     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6097                                          fromY, fromX, toY, toX, promoChar);
6098     /* [HGM] but possibly ignore an IllegalMove result */
6099     if (appData.testLegality) {
6100         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6101             DisplayMoveError(_("Illegal move"));
6102             return;
6103         }
6104     }
6105
6106     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6107 }
6108
6109 /* Common tail of UserMoveEvent and DropMenuEvent */
6110 int
6111 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6112      ChessMove moveType;
6113      int fromX, fromY, toX, toY;
6114      /*char*/int promoChar;
6115 {
6116     char *bookHit = 0;
6117
6118     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6119         // [HGM] superchess: suppress promotions to non-available piece
6120         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6121         if(WhiteOnMove(currentMove)) {
6122             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6123         } else {
6124             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6125         }
6126     }
6127
6128     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6129        move type in caller when we know the move is a legal promotion */
6130     if(moveType == NormalMove && promoChar)
6131         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6132
6133     /* [HGM] <popupFix> The following if has been moved here from
6134        UserMoveEvent(). Because it seemed to belong here (why not allow
6135        piece drops in training games?), and because it can only be
6136        performed after it is known to what we promote. */
6137     if (gameMode == Training) {
6138       /* compare the move played on the board to the next move in the
6139        * game. If they match, display the move and the opponent's response.
6140        * If they don't match, display an error message.
6141        */
6142       int saveAnimate;
6143       Board testBoard;
6144       CopyBoard(testBoard, boards[currentMove]);
6145       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6146
6147       if (CompareBoards(testBoard, boards[currentMove+1])) {
6148         ForwardInner(currentMove+1);
6149
6150         /* Autoplay the opponent's response.
6151          * if appData.animate was TRUE when Training mode was entered,
6152          * the response will be animated.
6153          */
6154         saveAnimate = appData.animate;
6155         appData.animate = animateTraining;
6156         ForwardInner(currentMove+1);
6157         appData.animate = saveAnimate;
6158
6159         /* check for the end of the game */
6160         if (currentMove >= forwardMostMove) {
6161           gameMode = PlayFromGameFile;
6162           ModeHighlight();
6163           SetTrainingModeOff();
6164           DisplayInformation(_("End of game"));
6165         }
6166       } else {
6167         DisplayError(_("Incorrect move"), 0);
6168       }
6169       return 1;
6170     }
6171
6172   /* Ok, now we know that the move is good, so we can kill
6173      the previous line in Analysis Mode */
6174   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6175                                 && currentMove < forwardMostMove) {
6176     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6177     else forwardMostMove = currentMove;
6178   }
6179
6180   /* If we need the chess program but it's dead, restart it */
6181   ResurrectChessProgram();
6182
6183   /* A user move restarts a paused game*/
6184   if (pausing)
6185     PauseEvent();
6186
6187   thinkOutput[0] = NULLCHAR;
6188
6189   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6190
6191   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6192
6193   if (gameMode == BeginningOfGame) {
6194     if (appData.noChessProgram) {
6195       gameMode = EditGame;
6196       SetGameInfo();
6197     } else {
6198       char buf[MSG_SIZ];
6199       gameMode = MachinePlaysBlack;
6200       StartClocks();
6201       SetGameInfo();
6202       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6203       DisplayTitle(buf);
6204       if (first.sendName) {
6205         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6206         SendToProgram(buf, &first);
6207       }
6208       StartClocks();
6209     }
6210     ModeHighlight();
6211   }
6212
6213   /* Relay move to ICS or chess engine */
6214   if (appData.icsActive) {
6215     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6216         gameMode == IcsExamining) {
6217       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6218         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6219         SendToICS("draw ");
6220         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6221       }
6222       // also send plain move, in case ICS does not understand atomic claims
6223       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6224       ics_user_moved = 1;
6225     }
6226   } else {
6227     if (first.sendTime && (gameMode == BeginningOfGame ||
6228                            gameMode == MachinePlaysWhite ||
6229                            gameMode == MachinePlaysBlack)) {
6230       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6231     }
6232     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6233          // [HGM] book: if program might be playing, let it use book
6234         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6235         first.maybeThinking = TRUE;
6236     } else SendMoveToProgram(forwardMostMove-1, &first);
6237     if (currentMove == cmailOldMove + 1) {
6238       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6239     }
6240   }
6241
6242   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6243
6244   switch (gameMode) {
6245   case EditGame:
6246     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6247     case MT_NONE:
6248     case MT_CHECK:
6249       break;
6250     case MT_CHECKMATE:
6251     case MT_STAINMATE:
6252       if (WhiteOnMove(currentMove)) {
6253         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6254       } else {
6255         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6256       }
6257       break;
6258     case MT_STALEMATE:
6259       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6260       break;
6261     }
6262     break;
6263
6264   case MachinePlaysBlack:
6265   case MachinePlaysWhite:
6266     /* disable certain menu options while machine is thinking */
6267     SetMachineThinkingEnables();
6268     break;
6269
6270   default:
6271     break;
6272   }
6273
6274   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6275
6276   if(bookHit) { // [HGM] book: simulate book reply
6277         static char bookMove[MSG_SIZ]; // a bit generous?
6278
6279         programStats.nodes = programStats.depth = programStats.time =
6280         programStats.score = programStats.got_only_move = 0;
6281         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6282
6283         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6284         strcat(bookMove, bookHit);
6285         HandleMachineMove(bookMove, &first);
6286   }
6287   return 1;
6288 }
6289
6290 void
6291 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6292      Board board;
6293      int flags;
6294      ChessMove kind;
6295      int rf, ff, rt, ft;
6296      VOIDSTAR closure;
6297 {
6298     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6299     Markers *m = (Markers *) closure;
6300     if(rf == fromY && ff == fromX)
6301         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6302                          || kind == WhiteCapturesEnPassant
6303                          || kind == BlackCapturesEnPassant);
6304     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6305 }
6306
6307 void
6308 MarkTargetSquares(int clear)
6309 {
6310   int x, y;
6311   if(!appData.markers || !appData.highlightDragging ||
6312      !appData.testLegality || gameMode == EditPosition) return;
6313   if(clear) {
6314     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6315   } else {
6316     int capt = 0;
6317     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6318     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6319       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6320       if(capt)
6321       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6322     }
6323   }
6324   DrawPosition(TRUE, NULL);
6325 }
6326
6327 int
6328 Explode(Board board, int fromX, int fromY, int toX, int toY)
6329 {
6330     if(gameInfo.variant == VariantAtomic &&
6331        (board[toY][toX] != EmptySquare ||                     // capture?
6332         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6333                          board[fromY][fromX] == BlackPawn   )
6334       )) {
6335         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6336         return TRUE;
6337     }
6338     return FALSE;
6339 }
6340
6341 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6342
6343 void LeftClick(ClickType clickType, int xPix, int yPix)
6344 {
6345     int x, y;
6346     Boolean saveAnimate;
6347     static int second = 0, promotionChoice = 0, dragging = 0;
6348     char promoChoice = NULLCHAR;
6349
6350     if(appData.seekGraph && appData.icsActive && loggedOn &&
6351         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6352         SeekGraphClick(clickType, xPix, yPix, 0);
6353         return;
6354     }
6355
6356     if (clickType == Press) ErrorPopDown();
6357     MarkTargetSquares(1);
6358
6359     x = EventToSquare(xPix, BOARD_WIDTH);
6360     y = EventToSquare(yPix, BOARD_HEIGHT);
6361     if (!flipView && y >= 0) {
6362         y = BOARD_HEIGHT - 1 - y;
6363     }
6364     if (flipView && x >= 0) {
6365         x = BOARD_WIDTH - 1 - x;
6366     }
6367
6368     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6369         if(clickType == Release) return; // ignore upclick of click-click destination
6370         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6371         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6372         if(gameInfo.holdingsWidth &&
6373                 (WhiteOnMove(currentMove)
6374                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6375                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6376             // click in right holdings, for determining promotion piece
6377             ChessSquare p = boards[currentMove][y][x];
6378             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6379             if(p != EmptySquare) {
6380                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6381                 fromX = fromY = -1;
6382                 return;
6383             }
6384         }
6385         DrawPosition(FALSE, boards[currentMove]);
6386         return;
6387     }
6388
6389     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6390     if(clickType == Press
6391             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6392               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6393               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6394         return;
6395
6396     autoQueen = appData.alwaysPromoteToQueen;
6397
6398     if (fromX == -1) {
6399       gatingPiece = EmptySquare;
6400       if (clickType != Press) {
6401         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6402             DragPieceEnd(xPix, yPix); dragging = 0;
6403             DrawPosition(FALSE, NULL);
6404         }
6405         return;
6406       }
6407       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6408             /* First square */
6409             if (OKToStartUserMove(x, y)) {
6410                 fromX = x;
6411                 fromY = y;
6412                 second = 0;
6413                 MarkTargetSquares(0);
6414                 DragPieceBegin(xPix, yPix); dragging = 1;
6415                 if (appData.highlightDragging) {
6416                     SetHighlights(x, y, -1, -1);
6417                 }
6418             }
6419             return;
6420         }
6421     }
6422
6423     /* fromX != -1 */
6424     if (clickType == Press && gameMode != EditPosition) {
6425         ChessSquare fromP;
6426         ChessSquare toP;
6427         int frc;
6428
6429         // ignore off-board to clicks
6430         if(y < 0 || x < 0) return;
6431
6432         /* Check if clicking again on the same color piece */
6433         fromP = boards[currentMove][fromY][fromX];
6434         toP = boards[currentMove][y][x];
6435         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6436         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6437              WhitePawn <= toP && toP <= WhiteKing &&
6438              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6439              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6440             (BlackPawn <= fromP && fromP <= BlackKing &&
6441              BlackPawn <= toP && toP <= BlackKing &&
6442              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6443              !(fromP == BlackKing && toP == BlackRook && frc))) {
6444             /* Clicked again on same color piece -- changed his mind */
6445             second = (x == fromX && y == fromY);
6446            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6447             if (appData.highlightDragging) {
6448                 SetHighlights(x, y, -1, -1);
6449             } else {
6450                 ClearHighlights();
6451             }
6452             if (OKToStartUserMove(x, y)) {
6453                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6454                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6455                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6456                  gatingPiece = boards[currentMove][fromY][fromX];
6457                 else gatingPiece = EmptySquare;
6458                 fromX = x;
6459                 fromY = y; dragging = 1;
6460                 MarkTargetSquares(0);
6461                 DragPieceBegin(xPix, yPix);
6462             }
6463            }
6464            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6465            second = FALSE; 
6466         }
6467         // ignore clicks on holdings
6468         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6469     }
6470
6471     if (clickType == Release && x == fromX && y == fromY) {
6472         DragPieceEnd(xPix, yPix); dragging = 0;
6473         if (appData.animateDragging) {
6474             /* Undo animation damage if any */
6475             DrawPosition(FALSE, NULL);
6476         }
6477         if (second) {
6478             /* Second up/down in same square; just abort move */
6479             second = 0;
6480             fromX = fromY = -1;
6481             gatingPiece = EmptySquare;
6482             ClearHighlights();
6483             gotPremove = 0;
6484             ClearPremoveHighlights();
6485         } else {
6486             /* First upclick in same square; start click-click mode */
6487             SetHighlights(x, y, -1, -1);
6488         }
6489         return;
6490     }
6491
6492     /* we now have a different from- and (possibly off-board) to-square */
6493     /* Completed move */
6494     toX = x;
6495     toY = y;
6496     saveAnimate = appData.animate;
6497     if (clickType == Press) {
6498         /* Finish clickclick move */
6499         if (appData.animate || appData.highlightLastMove) {
6500             SetHighlights(fromX, fromY, toX, toY);
6501         } else {
6502             ClearHighlights();
6503         }
6504     } else {
6505         /* Finish drag move */
6506         if (appData.highlightLastMove) {
6507             SetHighlights(fromX, fromY, toX, toY);
6508         } else {
6509             ClearHighlights();
6510         }
6511         DragPieceEnd(xPix, yPix); dragging = 0;
6512         /* Don't animate move and drag both */
6513         appData.animate = FALSE;
6514     }
6515
6516     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6517     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6518         ChessSquare piece = boards[currentMove][fromY][fromX];
6519         if(gameMode == EditPosition && piece != EmptySquare &&
6520            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6521             int n;
6522
6523             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6524                 n = PieceToNumber(piece - (int)BlackPawn);
6525                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6526                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6527                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6528             } else
6529             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6530                 n = PieceToNumber(piece);
6531                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6532                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6533                 boards[currentMove][n][BOARD_WIDTH-2]++;
6534             }
6535             boards[currentMove][fromY][fromX] = EmptySquare;
6536         }
6537         ClearHighlights();
6538         fromX = fromY = -1;
6539         DrawPosition(TRUE, boards[currentMove]);
6540         return;
6541     }
6542
6543     // off-board moves should not be highlighted
6544     if(x < 0 || y < 0) ClearHighlights();
6545
6546     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6547
6548     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6549         SetHighlights(fromX, fromY, toX, toY);
6550         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6551             // [HGM] super: promotion to captured piece selected from holdings
6552             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6553             promotionChoice = TRUE;
6554             // kludge follows to temporarily execute move on display, without promoting yet
6555             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6556             boards[currentMove][toY][toX] = p;
6557             DrawPosition(FALSE, boards[currentMove]);
6558             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6559             boards[currentMove][toY][toX] = q;
6560             DisplayMessage("Click in holdings to choose piece", "");
6561             return;
6562         }
6563         PromotionPopUp();
6564     } else {
6565         int oldMove = currentMove;
6566         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6567         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6568         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6569         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6570            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6571             DrawPosition(TRUE, boards[currentMove]);
6572         fromX = fromY = -1;
6573     }
6574     appData.animate = saveAnimate;
6575     if (appData.animate || appData.animateDragging) {
6576         /* Undo animation damage if needed */
6577         DrawPosition(FALSE, NULL);
6578     }
6579 }
6580
6581 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6582 {   // front-end-free part taken out of PieceMenuPopup
6583     int whichMenu; int xSqr, ySqr;
6584
6585     if(seekGraphUp) { // [HGM] seekgraph
6586         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6587         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6588         return -2;
6589     }
6590
6591     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6592          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6593         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6594         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6595         if(action == Press)   {
6596             originalFlip = flipView;
6597             flipView = !flipView; // temporarily flip board to see game from partners perspective
6598             DrawPosition(TRUE, partnerBoard);
6599             DisplayMessage(partnerStatus, "");
6600             partnerUp = TRUE;
6601         } else if(action == Release) {
6602             flipView = originalFlip;
6603             DrawPosition(TRUE, boards[currentMove]);
6604             partnerUp = FALSE;
6605         }
6606         return -2;
6607     }
6608
6609     xSqr = EventToSquare(x, BOARD_WIDTH);
6610     ySqr = EventToSquare(y, BOARD_HEIGHT);
6611     if (action == Release) UnLoadPV(); // [HGM] pv
6612     if (action != Press) return -2; // return code to be ignored
6613     switch (gameMode) {
6614       case IcsExamining:
6615         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6616       case EditPosition:
6617         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6618         if (xSqr < 0 || ySqr < 0) return -1;\r
6619         whichMenu = 0; // edit-position menu
6620         break;
6621       case IcsObserving:
6622         if(!appData.icsEngineAnalyze) return -1;
6623       case IcsPlayingWhite:
6624       case IcsPlayingBlack:
6625         if(!appData.zippyPlay) goto noZip;
6626       case AnalyzeMode:
6627       case AnalyzeFile:
6628       case MachinePlaysWhite:
6629       case MachinePlaysBlack:
6630       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6631         if (!appData.dropMenu) {
6632           LoadPV(x, y);
6633           return 2; // flag front-end to grab mouse events
6634         }
6635         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6636            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6637       case EditGame:
6638       noZip:
6639         if (xSqr < 0 || ySqr < 0) return -1;
6640         if (!appData.dropMenu || appData.testLegality &&
6641             gameInfo.variant != VariantBughouse &&
6642             gameInfo.variant != VariantCrazyhouse) return -1;
6643         whichMenu = 1; // drop menu
6644         break;
6645       default:
6646         return -1;
6647     }
6648
6649     if (((*fromX = xSqr) < 0) ||
6650         ((*fromY = ySqr) < 0)) {
6651         *fromX = *fromY = -1;
6652         return -1;
6653     }
6654     if (flipView)
6655       *fromX = BOARD_WIDTH - 1 - *fromX;
6656     else
6657       *fromY = BOARD_HEIGHT - 1 - *fromY;
6658
6659     return whichMenu;
6660 }
6661
6662 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6663 {
6664 //    char * hint = lastHint;
6665     FrontEndProgramStats stats;
6666
6667     stats.which = cps == &first ? 0 : 1;
6668     stats.depth = cpstats->depth;
6669     stats.nodes = cpstats->nodes;
6670     stats.score = cpstats->score;
6671     stats.time = cpstats->time;
6672     stats.pv = cpstats->movelist;
6673     stats.hint = lastHint;
6674     stats.an_move_index = 0;
6675     stats.an_move_count = 0;
6676
6677     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6678         stats.hint = cpstats->move_name;
6679         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6680         stats.an_move_count = cpstats->nr_moves;
6681     }
6682
6683     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
6684
6685     SetProgramStats( &stats );
6686 }
6687
6688 void
6689 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6690 {       // count all piece types
6691         int p, f, r;
6692         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6693         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6694         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6695                 p = board[r][f];
6696                 pCnt[p]++;
6697                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6698                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6699                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6700                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6701                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6702                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6703         }
6704 }
6705
6706 int
6707 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6708 {
6709         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6710         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6711
6712         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6713         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6714         if(myPawns == 2 && nMine == 3) // KPP
6715             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6716         if(myPawns == 1 && nMine == 2) // KP
6717             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6718         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6719             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6720         if(myPawns) return FALSE;
6721         if(pCnt[WhiteRook+side])
6722             return pCnt[BlackRook-side] ||
6723                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6724                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6725                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6726         if(pCnt[WhiteCannon+side]) {
6727             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6728             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6729         }
6730         if(pCnt[WhiteKnight+side])
6731             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6732         return FALSE;
6733 }
6734
6735 int
6736 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6737 {
6738         VariantClass v = gameInfo.variant;
6739
6740         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6741         if(v == VariantShatranj) return TRUE; // always winnable through baring
6742         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6743         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6744
6745         if(v == VariantXiangqi) {
6746                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6747
6748                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6749                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6750                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6751                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6752                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6753                 if(stale) // we have at least one last-rank P plus perhaps C
6754                     return majors // KPKX
6755                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6756                 else // KCA*E*
6757                     return pCnt[WhiteFerz+side] // KCAK
6758                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6759                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6760                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6761
6762         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6763                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6764
6765                 if(nMine == 1) return FALSE; // bare King
6766                 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
6767                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6768                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6769                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6770                 if(pCnt[WhiteKnight+side])
6771                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6772                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6773                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6774                 if(nBishops)
6775                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6776                 if(pCnt[WhiteAlfil+side])
6777                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6778                 if(pCnt[WhiteWazir+side])
6779                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6780         }
6781
6782         return TRUE;
6783 }
6784
6785 int
6786 Adjudicate(ChessProgramState *cps)
6787 {       // [HGM] some adjudications useful with buggy engines
6788         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6789         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6790         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6791         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6792         int k, count = 0; static int bare = 1;
6793         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6794         Boolean canAdjudicate = !appData.icsActive;
6795
6796         // most tests only when we understand the game, i.e. legality-checking on
6797             if( appData.testLegality )
6798             {   /* [HGM] Some more adjudications for obstinate engines */
6799                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6800                 static int moveCount = 6;
6801                 ChessMove result;
6802                 char *reason = NULL;
6803
6804                 /* Count what is on board. */
6805                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6806
6807                 /* Some material-based adjudications that have to be made before stalemate test */
6808                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6809                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6810                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6811                      if(canAdjudicate && appData.checkMates) {
6812                          if(engineOpponent)
6813                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6814                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6815                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6816                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6817                          return 1;
6818                      }
6819                 }
6820
6821                 /* Bare King in Shatranj (loses) or Losers (wins) */
6822                 if( nrW == 1 || nrB == 1) {
6823                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6824                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6825                      if(canAdjudicate && appData.checkMates) {
6826                          if(engineOpponent)
6827                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6828                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6829                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6830                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6831                          return 1;
6832                      }
6833                   } else
6834                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6835                   {    /* bare King */
6836                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6837                         if(canAdjudicate && appData.checkMates) {
6838                             /* but only adjudicate if adjudication enabled */
6839                             if(engineOpponent)
6840                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6841                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6842                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6843                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6844                             return 1;
6845                         }
6846                   }
6847                 } else bare = 1;
6848
6849
6850             // don't wait for engine to announce game end if we can judge ourselves
6851             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6852               case MT_CHECK:
6853                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6854                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6855                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6856                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6857                             checkCnt++;
6858                         if(checkCnt >= 2) {
6859                             reason = "Xboard adjudication: 3rd check";
6860                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6861                             break;
6862                         }
6863                     }
6864                 }
6865               case MT_NONE:
6866               default:
6867                 break;
6868               case MT_STALEMATE:
6869               case MT_STAINMATE:
6870                 reason = "Xboard adjudication: Stalemate";
6871                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6872                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6873                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6874                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6875                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6876                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6877                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6878                                                                         EP_CHECKMATE : EP_WINS);
6879                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6880                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6881                 }
6882                 break;
6883               case MT_CHECKMATE:
6884                 reason = "Xboard adjudication: Checkmate";
6885                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6886                 break;
6887             }
6888
6889                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6890                     case EP_STALEMATE:
6891                         result = GameIsDrawn; break;
6892                     case EP_CHECKMATE:
6893                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6894                     case EP_WINS:
6895                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6896                     default:
6897                         result = EndOfFile;
6898                 }
6899                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6900                     if(engineOpponent)
6901                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6902                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6903                     GameEnds( result, reason, GE_XBOARD );
6904                     return 1;
6905                 }
6906
6907                 /* Next absolutely insufficient mating material. */
6908                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6909                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6910                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6911
6912                      /* always flag draws, for judging claims */
6913                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6914
6915                      if(canAdjudicate && appData.materialDraws) {
6916                          /* but only adjudicate them if adjudication enabled */
6917                          if(engineOpponent) {
6918                            SendToProgram("force\n", engineOpponent); // suppress reply
6919                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6920                          }
6921                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6922                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6923                          return 1;
6924                      }
6925                 }
6926
6927                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6928                 if(gameInfo.variant == VariantXiangqi ?
6929                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6930                  : nrW + nrB == 4 &&
6931                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6932                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6933                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6934                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6935                    ) ) {
6936                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6937                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6938                           if(engineOpponent) {
6939                             SendToProgram("force\n", engineOpponent); // suppress reply
6940                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6941                           }
6942                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6943                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6944                           return 1;
6945                      }
6946                 } else moveCount = 6;
6947             }
6948         if (appData.debugMode) { int i;
6949             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6950                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6951                     appData.drawRepeats);
6952             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6953               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6954
6955         }
6956
6957         // Repetition draws and 50-move rule can be applied independently of legality testing
6958
6959                 /* Check for rep-draws */
6960                 count = 0;
6961                 for(k = forwardMostMove-2;
6962                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6963                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6964                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6965                     k-=2)
6966                 {   int rights=0;
6967                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6968                         /* compare castling rights */
6969                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6970                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6971                                 rights++; /* King lost rights, while rook still had them */
6972                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6973                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6974                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6975                                    rights++; /* but at least one rook lost them */
6976                         }
6977                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6978                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6979                                 rights++;
6980                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6981                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6982                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6983                                    rights++;
6984                         }
6985                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6986                             && appData.drawRepeats > 1) {
6987                              /* adjudicate after user-specified nr of repeats */
6988                              int result = GameIsDrawn;
6989                              char *details = "XBoard adjudication: repetition draw";
6990                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6991                                 // [HGM] xiangqi: check for forbidden perpetuals
6992                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6993                                 for(m=forwardMostMove; m>k; m-=2) {
6994                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6995                                         ourPerpetual = 0; // the current mover did not always check
6996                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6997                                         hisPerpetual = 0; // the opponent did not always check
6998                                 }
6999                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7000                                                                         ourPerpetual, hisPerpetual);
7001                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7002                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7003                                     details = "Xboard adjudication: perpetual checking";
7004                                 } else
7005                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7006                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7007                                 } else
7008                                 // Now check for perpetual chases
7009                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7010                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7011                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7012                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7013                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7014                                         details = "Xboard adjudication: perpetual chasing";
7015                                     } else
7016                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7017                                         break; // Abort repetition-checking loop.
7018                                 }
7019                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7020                              }
7021                              if(engineOpponent) {
7022                                SendToProgram("force\n", engineOpponent); // suppress reply
7023                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7024                              }
7025                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7026                              GameEnds( result, details, GE_XBOARD );
7027                              return 1;
7028                         }
7029                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7030                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7031                     }
7032                 }
7033
7034                 /* Now we test for 50-move draws. Determine ply count */
7035                 count = forwardMostMove;
7036                 /* look for last irreversble move */
7037                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7038                     count--;
7039                 /* if we hit starting position, add initial plies */
7040                 if( count == backwardMostMove )
7041                     count -= initialRulePlies;
7042                 count = forwardMostMove - count;
7043                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7044                         // adjust reversible move counter for checks in Xiangqi
7045                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7046                         if(i < backwardMostMove) i = backwardMostMove;
7047                         while(i <= forwardMostMove) {
7048                                 lastCheck = inCheck; // check evasion does not count
7049                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7050                                 if(inCheck || lastCheck) count--; // check does not count
7051                                 i++;
7052                         }
7053                 }
7054                 if( count >= 100)
7055                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7056                          /* this is used to judge if draw claims are legal */
7057                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7058                          if(engineOpponent) {
7059                            SendToProgram("force\n", engineOpponent); // suppress reply
7060                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7061                          }
7062                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7063                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7064                          return 1;
7065                 }
7066
7067                 /* if draw offer is pending, treat it as a draw claim
7068                  * when draw condition present, to allow engines a way to
7069                  * claim draws before making their move to avoid a race
7070                  * condition occurring after their move
7071                  */
7072                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7073                          char *p = NULL;
7074                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7075                              p = "Draw claim: 50-move rule";
7076                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7077                              p = "Draw claim: 3-fold repetition";
7078                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7079                              p = "Draw claim: insufficient mating material";
7080                          if( p != NULL && canAdjudicate) {
7081                              if(engineOpponent) {
7082                                SendToProgram("force\n", engineOpponent); // suppress reply
7083                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7084                              }
7085                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7086                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7087                              return 1;
7088                          }
7089                 }
7090
7091                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7092                     if(engineOpponent) {
7093                       SendToProgram("force\n", engineOpponent); // suppress reply
7094                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7095                     }
7096                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7097                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7098                     return 1;
7099                 }
7100         return 0;
7101 }
7102
7103 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7104 {   // [HGM] book: this routine intercepts moves to simulate book replies
7105     char *bookHit = NULL;
7106
7107     //first determine if the incoming move brings opponent into his book
7108     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7109         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7110     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7111     if(bookHit != NULL && !cps->bookSuspend) {
7112         // make sure opponent is not going to reply after receiving move to book position
7113         SendToProgram("force\n", cps);
7114         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7115     }
7116     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7117     // now arrange restart after book miss
7118     if(bookHit) {
7119         // after a book hit we never send 'go', and the code after the call to this routine
7120         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7121         char buf[MSG_SIZ];
7122         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7123         SendToProgram(buf, cps);
7124         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7125     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7126         SendToProgram("go\n", cps);
7127         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7128     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7129         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7130             SendToProgram("go\n", cps);
7131         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7132     }
7133     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7134 }
7135
7136 char *savedMessage;
7137 ChessProgramState *savedState;
7138 void DeferredBookMove(void)
7139 {
7140         if(savedState->lastPing != savedState->lastPong)
7141                     ScheduleDelayedEvent(DeferredBookMove, 10);
7142         else
7143         HandleMachineMove(savedMessage, savedState);
7144 }
7145
7146 void
7147 HandleMachineMove(message, cps)
7148      char *message;
7149      ChessProgramState *cps;
7150 {
7151     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7152     char realname[MSG_SIZ];
7153     int fromX, fromY, toX, toY;
7154     ChessMove moveType;
7155     char promoChar;
7156     char *p;
7157     int machineWhite;
7158     char *bookHit;
7159
7160     cps->userError = 0;
7161
7162 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7163     /*
7164      * Kludge to ignore BEL characters
7165      */
7166     while (*message == '\007') message++;
7167
7168     /*
7169      * [HGM] engine debug message: ignore lines starting with '#' character
7170      */
7171     if(cps->debug && *message == '#') return;
7172
7173     /*
7174      * Look for book output
7175      */
7176     if (cps == &first && bookRequested) {
7177         if (message[0] == '\t' || message[0] == ' ') {
7178             /* Part of the book output is here; append it */
7179             strcat(bookOutput, message);
7180             strcat(bookOutput, "  \n");
7181             return;
7182         } else if (bookOutput[0] != NULLCHAR) {
7183             /* All of book output has arrived; display it */
7184             char *p = bookOutput;
7185             while (*p != NULLCHAR) {
7186                 if (*p == '\t') *p = ' ';
7187                 p++;
7188             }
7189             DisplayInformation(bookOutput);
7190             bookRequested = FALSE;
7191             /* Fall through to parse the current output */
7192         }
7193     }
7194
7195     /*
7196      * Look for machine move.
7197      */
7198     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7199         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7200     {
7201         /* This method is only useful on engines that support ping */
7202         if (cps->lastPing != cps->lastPong) {
7203           if (gameMode == BeginningOfGame) {
7204             /* Extra move from before last new; ignore */
7205             if (appData.debugMode) {
7206                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7207             }
7208           } else {
7209             if (appData.debugMode) {
7210                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7211                         cps->which, gameMode);
7212             }
7213
7214             SendToProgram("undo\n", cps);
7215           }
7216           return;
7217         }
7218
7219         switch (gameMode) {
7220           case BeginningOfGame:
7221             /* Extra move from before last reset; ignore */
7222             if (appData.debugMode) {
7223                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7224             }
7225             return;
7226
7227           case EndOfGame:
7228           case IcsIdle:
7229           default:
7230             /* Extra move after we tried to stop.  The mode test is
7231                not a reliable way of detecting this problem, but it's
7232                the best we can do on engines that don't support ping.
7233             */
7234             if (appData.debugMode) {
7235                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7236                         cps->which, gameMode);
7237             }
7238             SendToProgram("undo\n", cps);
7239             return;
7240
7241           case MachinePlaysWhite:
7242           case IcsPlayingWhite:
7243             machineWhite = TRUE;
7244             break;
7245
7246           case MachinePlaysBlack:
7247           case IcsPlayingBlack:
7248             machineWhite = FALSE;
7249             break;
7250
7251           case TwoMachinesPlay:
7252             machineWhite = (cps->twoMachinesColor[0] == 'w');
7253             break;
7254         }
7255         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7256             if (appData.debugMode) {
7257                 fprintf(debugFP,
7258                         "Ignoring move out of turn by %s, gameMode %d"
7259                         ", forwardMost %d\n",
7260                         cps->which, gameMode, forwardMostMove);
7261             }
7262             return;
7263         }
7264
7265     if (appData.debugMode) { int f = forwardMostMove;
7266         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7267                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7268                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7269     }
7270         if(cps->alphaRank) AlphaRank(machineMove, 4);
7271         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7272                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7273             /* Machine move could not be parsed; ignore it. */
7274           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7275                     machineMove, cps->which);
7276             DisplayError(buf1, 0);
7277             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7278                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7279             if (gameMode == TwoMachinesPlay) {
7280               GameEnds(machineWhite ? BlackWins : WhiteWins,
7281                        buf1, GE_XBOARD);
7282             }
7283             return;
7284         }
7285
7286         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7287         /* So we have to redo legality test with true e.p. status here,  */
7288         /* to make sure an illegal e.p. capture does not slip through,   */
7289         /* to cause a forfeit on a justified illegal-move complaint      */
7290         /* of the opponent.                                              */
7291         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7292            ChessMove moveType;
7293            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7294                              fromY, fromX, toY, toX, promoChar);
7295             if (appData.debugMode) {
7296                 int i;
7297                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7298                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7299                 fprintf(debugFP, "castling rights\n");
7300             }
7301             if(moveType == IllegalMove) {
7302               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7303                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7304                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7305                            buf1, GE_XBOARD);
7306                 return;
7307            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7308            /* [HGM] Kludge to handle engines that send FRC-style castling
7309               when they shouldn't (like TSCP-Gothic) */
7310            switch(moveType) {
7311              case WhiteASideCastleFR:
7312              case BlackASideCastleFR:
7313                toX+=2;
7314                currentMoveString[2]++;
7315                break;
7316              case WhiteHSideCastleFR:
7317              case BlackHSideCastleFR:
7318                toX--;
7319                currentMoveString[2]--;
7320                break;
7321              default: ; // nothing to do, but suppresses warning of pedantic compilers
7322            }
7323         }
7324         hintRequested = FALSE;
7325         lastHint[0] = NULLCHAR;
7326         bookRequested = FALSE;
7327         /* Program may be pondering now */
7328         cps->maybeThinking = TRUE;
7329         if (cps->sendTime == 2) cps->sendTime = 1;
7330         if (cps->offeredDraw) cps->offeredDraw--;
7331
7332         /* currentMoveString is set as a side-effect of ParseOneMove */
7333         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7334         strcat(machineMove, "\n");
7335         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7336
7337         /* [AS] Save move info*/
7338         pvInfoList[ forwardMostMove ].score = programStats.score;
7339         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7340         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7341
7342         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7343
7344         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7345         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7346             int count = 0;
7347
7348             while( count < adjudicateLossPlies ) {
7349                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7350
7351                 if( count & 1 ) {
7352                     score = -score; /* Flip score for winning side */
7353                 }
7354
7355                 if( score > adjudicateLossThreshold ) {
7356                     break;
7357                 }
7358
7359                 count++;
7360             }
7361
7362             if( count >= adjudicateLossPlies ) {
7363                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7364
7365                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7366                     "Xboard adjudication",
7367                     GE_XBOARD );
7368
7369                 return;
7370             }
7371         }
7372
7373         if(Adjudicate(cps)) {
7374             DrawPosition(FALSE, boards[currentMove = forwardMostMove-1]);
7375             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7376             return; // [HGM] adjudicate: for all automatic game ends
7377         }
7378
7379 #if ZIPPY
7380         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7381             first.initDone) {
7382           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7383                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7384                 SendToICS("draw ");
7385                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7386           }
7387           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7388           ics_user_moved = 1;
7389           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7390                 char buf[3*MSG_SIZ];
7391
7392                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7393                         programStats.score / 100.,
7394                         programStats.depth,
7395                         programStats.time / 100.,
7396                         (unsigned int)programStats.nodes,
7397                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7398                         programStats.movelist);
7399                 SendToICS(buf);
7400 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7401           }
7402         }
7403 #endif
7404
7405         /* [AS] Clear stats for next move */
7406         ClearProgramStats();
7407         thinkOutput[0] = NULLCHAR;
7408         hiddenThinkOutputState = 0;
7409
7410         bookHit = NULL;
7411         if (gameMode == TwoMachinesPlay) {
7412             /* [HGM] relaying draw offers moved to after reception of move */
7413             /* and interpreting offer as claim if it brings draw condition */
7414             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7415                 SendToProgram("draw\n", cps->other);
7416             }
7417             if (cps->other->sendTime) {
7418                 SendTimeRemaining(cps->other,
7419                                   cps->other->twoMachinesColor[0] == 'w');
7420             }
7421             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7422             if (firstMove && !bookHit) {
7423                 firstMove = FALSE;
7424                 if (cps->other->useColors) {
7425                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7426                 }
7427                 SendToProgram("go\n", cps->other);
7428             }
7429             cps->other->maybeThinking = TRUE;
7430         }
7431
7432         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7433
7434         if (!pausing && appData.ringBellAfterMoves) {
7435             RingBell();
7436         }
7437
7438         /*
7439          * Reenable menu items that were disabled while
7440          * machine was thinking
7441          */
7442         if (gameMode != TwoMachinesPlay)
7443             SetUserThinkingEnables();
7444
7445         // [HGM] book: after book hit opponent has received move and is now in force mode
7446         // force the book reply into it, and then fake that it outputted this move by jumping
7447         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7448         if(bookHit) {
7449                 static char bookMove[MSG_SIZ]; // a bit generous?
7450
7451                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7452                 strcat(bookMove, bookHit);
7453                 message = bookMove;
7454                 cps = cps->other;
7455                 programStats.nodes = programStats.depth = programStats.time =
7456                 programStats.score = programStats.got_only_move = 0;
7457                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7458
7459                 if(cps->lastPing != cps->lastPong) {
7460                     savedMessage = message; // args for deferred call
7461                     savedState = cps;
7462                     ScheduleDelayedEvent(DeferredBookMove, 10);
7463                     return;
7464                 }
7465                 goto FakeBookMove;
7466         }
7467
7468         return;
7469     }
7470
7471     /* Set special modes for chess engines.  Later something general
7472      *  could be added here; for now there is just one kludge feature,
7473      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7474      *  when "xboard" is given as an interactive command.
7475      */
7476     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7477         cps->useSigint = FALSE;
7478         cps->useSigterm = FALSE;
7479     }
7480     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7481       ParseFeatures(message+8, cps);
7482       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7483     }
7484
7485     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7486       int dummy, s=6; char buf[MSG_SIZ];
7487       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7488       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7489       ParseFEN(boards[0], &dummy, message+s);
7490       DrawPosition(TRUE, boards[0]);
7491       startedFromSetupPosition = TRUE;
7492       return;
7493     }
7494     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7495      * want this, I was asked to put it in, and obliged.
7496      */
7497     if (!strncmp(message, "setboard ", 9)) {
7498         Board initial_position;
7499
7500         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7501
7502         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7503             DisplayError(_("Bad FEN received from engine"), 0);
7504             return ;
7505         } else {
7506            Reset(TRUE, FALSE);
7507            CopyBoard(boards[0], initial_position);
7508            initialRulePlies = FENrulePlies;
7509            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7510            else gameMode = MachinePlaysBlack;
7511            DrawPosition(FALSE, boards[currentMove]);
7512         }
7513         return;
7514     }
7515
7516     /*
7517      * Look for communication commands
7518      */
7519     if (!strncmp(message, "telluser ", 9)) {
7520         if(message[9] == '\\' && message[10] == '\\')
7521             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7522         DisplayNote(message + 9);
7523         return;
7524     }
7525     if (!strncmp(message, "tellusererror ", 14)) {
7526         cps->userError = 1;
7527         if(message[14] == '\\' && message[15] == '\\')
7528             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7529         DisplayError(message + 14, 0);
7530         return;
7531     }
7532     if (!strncmp(message, "tellopponent ", 13)) {
7533       if (appData.icsActive) {
7534         if (loggedOn) {
7535           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7536           SendToICS(buf1);
7537         }
7538       } else {
7539         DisplayNote(message + 13);
7540       }
7541       return;
7542     }
7543     if (!strncmp(message, "tellothers ", 11)) {
7544       if (appData.icsActive) {
7545         if (loggedOn) {
7546           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7547           SendToICS(buf1);
7548         }
7549       }
7550       return;
7551     }
7552     if (!strncmp(message, "tellall ", 8)) {
7553       if (appData.icsActive) {
7554         if (loggedOn) {
7555           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7556           SendToICS(buf1);
7557         }
7558       } else {
7559         DisplayNote(message + 8);
7560       }
7561       return;
7562     }
7563     if (strncmp(message, "warning", 7) == 0) {
7564         /* Undocumented feature, use tellusererror in new code */
7565         DisplayError(message, 0);
7566         return;
7567     }
7568     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7569         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7570         strcat(realname, " query");
7571         AskQuestion(realname, buf2, buf1, cps->pr);
7572         return;
7573     }
7574     /* Commands from the engine directly to ICS.  We don't allow these to be
7575      *  sent until we are logged on. Crafty kibitzes have been known to
7576      *  interfere with the login process.
7577      */
7578     if (loggedOn) {
7579         if (!strncmp(message, "tellics ", 8)) {
7580             SendToICS(message + 8);
7581             SendToICS("\n");
7582             return;
7583         }
7584         if (!strncmp(message, "tellicsnoalias ", 15)) {
7585             SendToICS(ics_prefix);
7586             SendToICS(message + 15);
7587             SendToICS("\n");
7588             return;
7589         }
7590         /* The following are for backward compatibility only */
7591         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7592             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7593             SendToICS(ics_prefix);
7594             SendToICS(message);
7595             SendToICS("\n");
7596             return;
7597         }
7598     }
7599     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7600         return;
7601     }
7602     /*
7603      * If the move is illegal, cancel it and redraw the board.
7604      * Also deal with other error cases.  Matching is rather loose
7605      * here to accommodate engines written before the spec.
7606      */
7607     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7608         strncmp(message, "Error", 5) == 0) {
7609         if (StrStr(message, "name") ||
7610             StrStr(message, "rating") || StrStr(message, "?") ||
7611             StrStr(message, "result") || StrStr(message, "board") ||
7612             StrStr(message, "bk") || StrStr(message, "computer") ||
7613             StrStr(message, "variant") || StrStr(message, "hint") ||
7614             StrStr(message, "random") || StrStr(message, "depth") ||
7615             StrStr(message, "accepted")) {
7616             return;
7617         }
7618         if (StrStr(message, "protover")) {
7619           /* Program is responding to input, so it's apparently done
7620              initializing, and this error message indicates it is
7621              protocol version 1.  So we don't need to wait any longer
7622              for it to initialize and send feature commands. */
7623           FeatureDone(cps, 1);
7624           cps->protocolVersion = 1;
7625           return;
7626         }
7627         cps->maybeThinking = FALSE;
7628
7629         if (StrStr(message, "draw")) {
7630             /* Program doesn't have "draw" command */
7631             cps->sendDrawOffers = 0;
7632             return;
7633         }
7634         if (cps->sendTime != 1 &&
7635             (StrStr(message, "time") || StrStr(message, "otim"))) {
7636           /* Program apparently doesn't have "time" or "otim" command */
7637           cps->sendTime = 0;
7638           return;
7639         }
7640         if (StrStr(message, "analyze")) {
7641             cps->analysisSupport = FALSE;
7642             cps->analyzing = FALSE;
7643             Reset(FALSE, TRUE);
7644             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7645             DisplayError(buf2, 0);
7646             return;
7647         }
7648         if (StrStr(message, "(no matching move)st")) {
7649           /* Special kludge for GNU Chess 4 only */
7650           cps->stKludge = TRUE;
7651           SendTimeControl(cps, movesPerSession, timeControl,
7652                           timeIncrement, appData.searchDepth,
7653                           searchTime);
7654           return;
7655         }
7656         if (StrStr(message, "(no matching move)sd")) {
7657           /* Special kludge for GNU Chess 4 only */
7658           cps->sdKludge = TRUE;
7659           SendTimeControl(cps, movesPerSession, timeControl,
7660                           timeIncrement, appData.searchDepth,
7661                           searchTime);
7662           return;
7663         }
7664         if (!StrStr(message, "llegal")) {
7665             return;
7666         }
7667         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7668             gameMode == IcsIdle) return;
7669         if (forwardMostMove <= backwardMostMove) return;
7670         if (pausing) PauseEvent();
7671       if(appData.forceIllegal) {
7672             // [HGM] illegal: machine refused move; force position after move into it
7673           SendToProgram("force\n", cps);
7674           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7675                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7676                 // when black is to move, while there might be nothing on a2 or black
7677                 // might already have the move. So send the board as if white has the move.
7678                 // But first we must change the stm of the engine, as it refused the last move
7679                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7680                 if(WhiteOnMove(forwardMostMove)) {
7681                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7682                     SendBoard(cps, forwardMostMove); // kludgeless board
7683                 } else {
7684                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7685                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7686                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7687                 }
7688           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7689             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7690                  gameMode == TwoMachinesPlay)
7691               SendToProgram("go\n", cps);
7692             return;
7693       } else
7694         if (gameMode == PlayFromGameFile) {
7695             /* Stop reading this game file */
7696             gameMode = EditGame;
7697             ModeHighlight();
7698         }
7699         /* [HGM] illegal-move claim should forfeit game when Xboard */
7700         /* only passes fully legal moves                            */
7701         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7702             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7703                                 "False illegal-move claim", GE_XBOARD );
7704             return; // do not take back move we tested as valid
7705         }
7706         currentMove = forwardMostMove-1;
7707         DisplayMove(currentMove-1); /* before DisplayMoveError */
7708         SwitchClocks(forwardMostMove-1); // [HGM] race
7709         DisplayBothClocks();
7710         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7711                 parseList[currentMove], cps->which);
7712         DisplayMoveError(buf1);
7713         DrawPosition(FALSE, boards[currentMove]);
7714         return;
7715     }
7716     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7717         /* Program has a broken "time" command that
7718            outputs a string not ending in newline.
7719            Don't use it. */
7720         cps->sendTime = 0;
7721     }
7722
7723     /*
7724      * If chess program startup fails, exit with an error message.
7725      * Attempts to recover here are futile.
7726      */
7727     if ((StrStr(message, "unknown host") != NULL)
7728         || (StrStr(message, "No remote directory") != NULL)
7729         || (StrStr(message, "not found") != NULL)
7730         || (StrStr(message, "No such file") != NULL)
7731         || (StrStr(message, "can't alloc") != NULL)
7732         || (StrStr(message, "Permission denied") != NULL)) {
7733
7734         cps->maybeThinking = FALSE;
7735         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7736                 cps->which, cps->program, cps->host, message);
7737         RemoveInputSource(cps->isr);
7738         DisplayFatalError(buf1, 0, 1);
7739         return;
7740     }
7741
7742     /*
7743      * Look for hint output
7744      */
7745     if (sscanf(message, "Hint: %s", buf1) == 1) {
7746         if (cps == &first && hintRequested) {
7747             hintRequested = FALSE;
7748             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7749                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7750                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7751                                     PosFlags(forwardMostMove),
7752                                     fromY, fromX, toY, toX, promoChar, buf1);
7753                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7754                 DisplayInformation(buf2);
7755             } else {
7756                 /* Hint move could not be parsed!? */
7757               snprintf(buf2, sizeof(buf2),
7758                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7759                         buf1, cps->which);
7760                 DisplayError(buf2, 0);
7761             }
7762         } else {
7763           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7764         }
7765         return;
7766     }
7767
7768     /*
7769      * Ignore other messages if game is not in progress
7770      */
7771     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7772         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7773
7774     /*
7775      * look for win, lose, draw, or draw offer
7776      */
7777     if (strncmp(message, "1-0", 3) == 0) {
7778         char *p, *q, *r = "";
7779         p = strchr(message, '{');
7780         if (p) {
7781             q = strchr(p, '}');
7782             if (q) {
7783                 *q = NULLCHAR;
7784                 r = p + 1;
7785             }
7786         }
7787         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7788         return;
7789     } else if (strncmp(message, "0-1", 3) == 0) {
7790         char *p, *q, *r = "";
7791         p = strchr(message, '{');
7792         if (p) {
7793             q = strchr(p, '}');
7794             if (q) {
7795                 *q = NULLCHAR;
7796                 r = p + 1;
7797             }
7798         }
7799         /* Kludge for Arasan 4.1 bug */
7800         if (strcmp(r, "Black resigns") == 0) {
7801             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7802             return;
7803         }
7804         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7805         return;
7806     } else if (strncmp(message, "1/2", 3) == 0) {
7807         char *p, *q, *r = "";
7808         p = strchr(message, '{');
7809         if (p) {
7810             q = strchr(p, '}');
7811             if (q) {
7812                 *q = NULLCHAR;
7813                 r = p + 1;
7814             }
7815         }
7816
7817         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7818         return;
7819
7820     } else if (strncmp(message, "White resign", 12) == 0) {
7821         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7822         return;
7823     } else if (strncmp(message, "Black resign", 12) == 0) {
7824         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7825         return;
7826     } else if (strncmp(message, "White matches", 13) == 0 ||
7827                strncmp(message, "Black matches", 13) == 0   ) {
7828         /* [HGM] ignore GNUShogi noises */
7829         return;
7830     } else if (strncmp(message, "White", 5) == 0 &&
7831                message[5] != '(' &&
7832                StrStr(message, "Black") == NULL) {
7833         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7834         return;
7835     } else if (strncmp(message, "Black", 5) == 0 &&
7836                message[5] != '(') {
7837         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7838         return;
7839     } else if (strcmp(message, "resign") == 0 ||
7840                strcmp(message, "computer resigns") == 0) {
7841         switch (gameMode) {
7842           case MachinePlaysBlack:
7843           case IcsPlayingBlack:
7844             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7845             break;
7846           case MachinePlaysWhite:
7847           case IcsPlayingWhite:
7848             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7849             break;
7850           case TwoMachinesPlay:
7851             if (cps->twoMachinesColor[0] == 'w')
7852               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7853             else
7854               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7855             break;
7856           default:
7857             /* can't happen */
7858             break;
7859         }
7860         return;
7861     } else if (strncmp(message, "opponent mates", 14) == 0) {
7862         switch (gameMode) {
7863           case MachinePlaysBlack:
7864           case IcsPlayingBlack:
7865             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7866             break;
7867           case MachinePlaysWhite:
7868           case IcsPlayingWhite:
7869             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7870             break;
7871           case TwoMachinesPlay:
7872             if (cps->twoMachinesColor[0] == 'w')
7873               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7874             else
7875               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7876             break;
7877           default:
7878             /* can't happen */
7879             break;
7880         }
7881         return;
7882     } else if (strncmp(message, "computer mates", 14) == 0) {
7883         switch (gameMode) {
7884           case MachinePlaysBlack:
7885           case IcsPlayingBlack:
7886             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7887             break;
7888           case MachinePlaysWhite:
7889           case IcsPlayingWhite:
7890             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7891             break;
7892           case TwoMachinesPlay:
7893             if (cps->twoMachinesColor[0] == 'w')
7894               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7895             else
7896               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7897             break;
7898           default:
7899             /* can't happen */
7900             break;
7901         }
7902         return;
7903     } else if (strncmp(message, "checkmate", 9) == 0) {
7904         if (WhiteOnMove(forwardMostMove)) {
7905             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7906         } else {
7907             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7908         }
7909         return;
7910     } else if (strstr(message, "Draw") != NULL ||
7911                strstr(message, "game is a draw") != NULL) {
7912         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7913         return;
7914     } else if (strstr(message, "offer") != NULL &&
7915                strstr(message, "draw") != NULL) {
7916 #if ZIPPY
7917         if (appData.zippyPlay && first.initDone) {
7918             /* Relay offer to ICS */
7919             SendToICS(ics_prefix);
7920             SendToICS("draw\n");
7921         }
7922 #endif
7923         cps->offeredDraw = 2; /* valid until this engine moves twice */
7924         if (gameMode == TwoMachinesPlay) {
7925             if (cps->other->offeredDraw) {
7926                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7927             /* [HGM] in two-machine mode we delay relaying draw offer      */
7928             /* until after we also have move, to see if it is really claim */
7929             }
7930         } else if (gameMode == MachinePlaysWhite ||
7931                    gameMode == MachinePlaysBlack) {
7932           if (userOfferedDraw) {
7933             DisplayInformation(_("Machine accepts your draw offer"));
7934             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7935           } else {
7936             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7937           }
7938         }
7939     }
7940
7941
7942     /*
7943      * Look for thinking output
7944      */
7945     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7946           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7947                                 ) {
7948         int plylev, mvleft, mvtot, curscore, time;
7949         char mvname[MOVE_LEN];
7950         u64 nodes; // [DM]
7951         char plyext;
7952         int ignore = FALSE;
7953         int prefixHint = FALSE;
7954         mvname[0] = NULLCHAR;
7955
7956         switch (gameMode) {
7957           case MachinePlaysBlack:
7958           case IcsPlayingBlack:
7959             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7960             break;
7961           case MachinePlaysWhite:
7962           case IcsPlayingWhite:
7963             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7964             break;
7965           case AnalyzeMode:
7966           case AnalyzeFile:
7967             break;
7968           case IcsObserving: /* [DM] icsEngineAnalyze */
7969             if (!appData.icsEngineAnalyze) ignore = TRUE;
7970             break;
7971           case TwoMachinesPlay:
7972             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7973                 ignore = TRUE;
7974             }
7975             break;
7976           default:
7977             ignore = TRUE;
7978             break;
7979         }
7980
7981         if (!ignore) {
7982             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7983             buf1[0] = NULLCHAR;
7984             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7985                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7986
7987                 if (plyext != ' ' && plyext != '\t') {
7988                     time *= 100;
7989                 }
7990
7991                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7992                 if( cps->scoreIsAbsolute &&
7993                     ( gameMode == MachinePlaysBlack ||
7994                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7995                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7996                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7997                      !WhiteOnMove(currentMove)
7998                     ) )
7999                 {
8000                     curscore = -curscore;
8001                 }
8002
8003
8004                 tempStats.depth = plylev;
8005                 tempStats.nodes = nodes;
8006                 tempStats.time = time;
8007                 tempStats.score = curscore;
8008                 tempStats.got_only_move = 0;
8009
8010                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8011                         int ticklen;
8012
8013                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8014                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8015                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8016                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8017                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8018                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8019                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8020                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8021                 }
8022
8023                 /* Buffer overflow protection */
8024                 if (buf1[0] != NULLCHAR) {
8025                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8026                         && appData.debugMode) {
8027                         fprintf(debugFP,
8028                                 "PV is too long; using the first %u bytes.\n",
8029                                 (unsigned) sizeof(tempStats.movelist) - 1);
8030                     }
8031
8032                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8033                 } else {
8034                     sprintf(tempStats.movelist, " no PV\n");
8035                 }
8036
8037                 if (tempStats.seen_stat) {
8038                     tempStats.ok_to_send = 1;
8039                 }
8040
8041                 if (strchr(tempStats.movelist, '(') != NULL) {
8042                     tempStats.line_is_book = 1;
8043                     tempStats.nr_moves = 0;
8044                     tempStats.moves_left = 0;
8045                 } else {
8046                     tempStats.line_is_book = 0;
8047                 }
8048
8049                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8050                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8051
8052                 SendProgramStatsToFrontend( cps, &tempStats );
8053
8054                 /*
8055                     [AS] Protect the thinkOutput buffer from overflow... this
8056                     is only useful if buf1 hasn't overflowed first!
8057                 */
8058                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8059                          plylev,
8060                          (gameMode == TwoMachinesPlay ?
8061                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8062                          ((double) curscore) / 100.0,
8063                          prefixHint ? lastHint : "",
8064                          prefixHint ? " " : "" );
8065
8066                 if( buf1[0] != NULLCHAR ) {
8067                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8068
8069                     if( strlen(buf1) > max_len ) {
8070                         if( appData.debugMode) {
8071                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8072                         }
8073                         buf1[max_len+1] = '\0';
8074                     }
8075
8076                     strcat( thinkOutput, buf1 );
8077                 }
8078
8079                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8080                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8081                     DisplayMove(currentMove - 1);
8082                 }
8083                 return;
8084
8085             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8086                 /* crafty (9.25+) says "(only move) <move>"
8087                  * if there is only 1 legal move
8088                  */
8089                 sscanf(p, "(only move) %s", buf1);
8090                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8091                 sprintf(programStats.movelist, "%s (only move)", buf1);
8092                 programStats.depth = 1;
8093                 programStats.nr_moves = 1;
8094                 programStats.moves_left = 1;
8095                 programStats.nodes = 1;
8096                 programStats.time = 1;
8097                 programStats.got_only_move = 1;
8098
8099                 /* Not really, but we also use this member to
8100                    mean "line isn't going to change" (Crafty
8101                    isn't searching, so stats won't change) */
8102                 programStats.line_is_book = 1;
8103
8104                 SendProgramStatsToFrontend( cps, &programStats );
8105
8106                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8107                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8108                     DisplayMove(currentMove - 1);
8109                 }
8110                 return;
8111             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8112                               &time, &nodes, &plylev, &mvleft,
8113                               &mvtot, mvname) >= 5) {
8114                 /* The stat01: line is from Crafty (9.29+) in response
8115                    to the "." command */
8116                 programStats.seen_stat = 1;
8117                 cps->maybeThinking = TRUE;
8118
8119                 if (programStats.got_only_move || !appData.periodicUpdates)
8120                   return;
8121
8122                 programStats.depth = plylev;
8123                 programStats.time = time;
8124                 programStats.nodes = nodes;
8125                 programStats.moves_left = mvleft;
8126                 programStats.nr_moves = mvtot;
8127                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8128                 programStats.ok_to_send = 1;
8129                 programStats.movelist[0] = '\0';
8130
8131                 SendProgramStatsToFrontend( cps, &programStats );
8132
8133                 return;
8134
8135             } else if (strncmp(message,"++",2) == 0) {
8136                 /* Crafty 9.29+ outputs this */
8137                 programStats.got_fail = 2;
8138                 return;
8139
8140             } else if (strncmp(message,"--",2) == 0) {
8141                 /* Crafty 9.29+ outputs this */
8142                 programStats.got_fail = 1;
8143                 return;
8144
8145             } else if (thinkOutput[0] != NULLCHAR &&
8146                        strncmp(message, "    ", 4) == 0) {
8147                 unsigned message_len;
8148
8149                 p = message;
8150                 while (*p && *p == ' ') p++;
8151
8152                 message_len = strlen( p );
8153
8154                 /* [AS] Avoid buffer overflow */
8155                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8156                     strcat(thinkOutput, " ");
8157                     strcat(thinkOutput, p);
8158                 }
8159
8160                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8161                     strcat(programStats.movelist, " ");
8162                     strcat(programStats.movelist, p);
8163                 }
8164
8165                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8166                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8167                     DisplayMove(currentMove - 1);
8168                 }
8169                 return;
8170             }
8171         }
8172         else {
8173             buf1[0] = NULLCHAR;
8174
8175             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8176                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8177             {
8178                 ChessProgramStats cpstats;
8179
8180                 if (plyext != ' ' && plyext != '\t') {
8181                     time *= 100;
8182                 }
8183
8184                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8185                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8186                     curscore = -curscore;
8187                 }
8188
8189                 cpstats.depth = plylev;
8190                 cpstats.nodes = nodes;
8191                 cpstats.time = time;
8192                 cpstats.score = curscore;
8193                 cpstats.got_only_move = 0;
8194                 cpstats.movelist[0] = '\0';
8195
8196                 if (buf1[0] != NULLCHAR) {
8197                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8198                 }
8199
8200                 cpstats.ok_to_send = 0;
8201                 cpstats.line_is_book = 0;
8202                 cpstats.nr_moves = 0;
8203                 cpstats.moves_left = 0;
8204
8205                 SendProgramStatsToFrontend( cps, &cpstats );
8206             }
8207         }
8208     }
8209 }
8210
8211
8212 /* Parse a game score from the character string "game", and
8213    record it as the history of the current game.  The game
8214    score is NOT assumed to start from the standard position.
8215    The display is not updated in any way.
8216    */
8217 void
8218 ParseGameHistory(game)
8219      char *game;
8220 {
8221     ChessMove moveType;
8222     int fromX, fromY, toX, toY, boardIndex;
8223     char promoChar;
8224     char *p, *q;
8225     char buf[MSG_SIZ];
8226
8227     if (appData.debugMode)
8228       fprintf(debugFP, "Parsing game history: %s\n", game);
8229
8230     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8231     gameInfo.site = StrSave(appData.icsHost);
8232     gameInfo.date = PGNDate();
8233     gameInfo.round = StrSave("-");
8234
8235     /* Parse out names of players */
8236     while (*game == ' ') game++;
8237     p = buf;
8238     while (*game != ' ') *p++ = *game++;
8239     *p = NULLCHAR;
8240     gameInfo.white = StrSave(buf);
8241     while (*game == ' ') game++;
8242     p = buf;
8243     while (*game != ' ' && *game != '\n') *p++ = *game++;
8244     *p = NULLCHAR;
8245     gameInfo.black = StrSave(buf);
8246
8247     /* Parse moves */
8248     boardIndex = blackPlaysFirst ? 1 : 0;
8249     yynewstr(game);
8250     for (;;) {
8251         yyboardindex = boardIndex;
8252         moveType = (ChessMove) Myylex();
8253         switch (moveType) {
8254           case IllegalMove:             /* maybe suicide chess, etc. */
8255   if (appData.debugMode) {
8256     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8257     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8258     setbuf(debugFP, NULL);
8259   }
8260           case WhitePromotion:
8261           case BlackPromotion:
8262           case WhiteNonPromotion:
8263           case BlackNonPromotion:
8264           case NormalMove:
8265           case WhiteCapturesEnPassant:
8266           case BlackCapturesEnPassant:
8267           case WhiteKingSideCastle:
8268           case WhiteQueenSideCastle:
8269           case BlackKingSideCastle:
8270           case BlackQueenSideCastle:
8271           case WhiteKingSideCastleWild:
8272           case WhiteQueenSideCastleWild:
8273           case BlackKingSideCastleWild:
8274           case BlackQueenSideCastleWild:
8275           /* PUSH Fabien */
8276           case WhiteHSideCastleFR:
8277           case WhiteASideCastleFR:
8278           case BlackHSideCastleFR:
8279           case BlackASideCastleFR:
8280           /* POP Fabien */
8281             fromX = currentMoveString[0] - AAA;
8282             fromY = currentMoveString[1] - ONE;
8283             toX = currentMoveString[2] - AAA;
8284             toY = currentMoveString[3] - ONE;
8285             promoChar = currentMoveString[4];
8286             break;
8287           case WhiteDrop:
8288           case BlackDrop:
8289             fromX = moveType == WhiteDrop ?
8290               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8291             (int) CharToPiece(ToLower(currentMoveString[0]));
8292             fromY = DROP_RANK;
8293             toX = currentMoveString[2] - AAA;
8294             toY = currentMoveString[3] - ONE;
8295             promoChar = NULLCHAR;
8296             break;
8297           case AmbiguousMove:
8298             /* bug? */
8299             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8300   if (appData.debugMode) {
8301     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8302     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8303     setbuf(debugFP, NULL);
8304   }
8305             DisplayError(buf, 0);
8306             return;
8307           case ImpossibleMove:
8308             /* bug? */
8309             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8310   if (appData.debugMode) {
8311     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8312     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8313     setbuf(debugFP, NULL);
8314   }
8315             DisplayError(buf, 0);
8316             return;
8317           case EndOfFile:
8318             if (boardIndex < backwardMostMove) {
8319                 /* Oops, gap.  How did that happen? */
8320                 DisplayError(_("Gap in move list"), 0);
8321                 return;
8322             }
8323             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8324             if (boardIndex > forwardMostMove) {
8325                 forwardMostMove = boardIndex;
8326             }
8327             return;
8328           case ElapsedTime:
8329             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8330                 strcat(parseList[boardIndex-1], " ");
8331                 strcat(parseList[boardIndex-1], yy_text);
8332             }
8333             continue;
8334           case Comment:
8335           case PGNTag:
8336           case NAG:
8337           default:
8338             /* ignore */
8339             continue;
8340           case WhiteWins:
8341           case BlackWins:
8342           case GameIsDrawn:
8343           case GameUnfinished:
8344             if (gameMode == IcsExamining) {
8345                 if (boardIndex < backwardMostMove) {
8346                     /* Oops, gap.  How did that happen? */
8347                     return;
8348                 }
8349                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8350                 return;
8351             }
8352             gameInfo.result = moveType;
8353             p = strchr(yy_text, '{');
8354             if (p == NULL) p = strchr(yy_text, '(');
8355             if (p == NULL) {
8356                 p = yy_text;
8357                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8358             } else {
8359                 q = strchr(p, *p == '{' ? '}' : ')');
8360                 if (q != NULL) *q = NULLCHAR;
8361                 p++;
8362             }
8363             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8364             gameInfo.resultDetails = StrSave(p);
8365             continue;
8366         }
8367         if (boardIndex >= forwardMostMove &&
8368             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8369             backwardMostMove = blackPlaysFirst ? 1 : 0;
8370             return;
8371         }
8372         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8373                                  fromY, fromX, toY, toX, promoChar,
8374                                  parseList[boardIndex]);
8375         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8376         /* currentMoveString is set as a side-effect of yylex */
8377         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8378         strcat(moveList[boardIndex], "\n");
8379         boardIndex++;
8380         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8381         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8382           case MT_NONE:
8383           case MT_STALEMATE:
8384           default:
8385             break;
8386           case MT_CHECK:
8387             if(gameInfo.variant != VariantShogi)
8388                 strcat(parseList[boardIndex - 1], "+");
8389             break;
8390           case MT_CHECKMATE:
8391           case MT_STAINMATE:
8392             strcat(parseList[boardIndex - 1], "#");
8393             break;
8394         }
8395     }
8396 }
8397
8398
8399 /* Apply a move to the given board  */
8400 void
8401 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8402      int fromX, fromY, toX, toY;
8403      int promoChar;
8404      Board board;
8405 {
8406   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8407   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8408
8409     /* [HGM] compute & store e.p. status and castling rights for new position */
8410     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8411
8412       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8413       oldEP = (signed char)board[EP_STATUS];
8414       board[EP_STATUS] = EP_NONE;
8415
8416       if( board[toY][toX] != EmptySquare )
8417            board[EP_STATUS] = EP_CAPTURE;
8418
8419   if (fromY == DROP_RANK) {
8420         /* must be first */
8421         piece = board[toY][toX] = (ChessSquare) fromX;
8422   } else {
8423       int i;
8424
8425       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8426            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8427                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8428       } else
8429       if( board[fromY][fromX] == WhitePawn ) {
8430            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8431                board[EP_STATUS] = EP_PAWN_MOVE;
8432            if( toY-fromY==2) {
8433                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8434                         gameInfo.variant != VariantBerolina || toX < fromX)
8435                       board[EP_STATUS] = toX | berolina;
8436                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8437                         gameInfo.variant != VariantBerolina || toX > fromX)
8438                       board[EP_STATUS] = toX;
8439            }
8440       } else
8441       if( board[fromY][fromX] == BlackPawn ) {
8442            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8443                board[EP_STATUS] = EP_PAWN_MOVE;
8444            if( toY-fromY== -2) {
8445                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8446                         gameInfo.variant != VariantBerolina || toX < fromX)
8447                       board[EP_STATUS] = toX | berolina;
8448                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8449                         gameInfo.variant != VariantBerolina || toX > fromX)
8450                       board[EP_STATUS] = toX;
8451            }
8452        }
8453
8454        for(i=0; i<nrCastlingRights; i++) {
8455            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8456               board[CASTLING][i] == toX   && castlingRank[i] == toY
8457              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8458        }
8459
8460      if (fromX == toX && fromY == toY) return;
8461
8462      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8463      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8464      if(gameInfo.variant == VariantKnightmate)
8465          king += (int) WhiteUnicorn - (int) WhiteKing;
8466
8467     /* Code added by Tord: */
8468     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8469     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8470         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8471       board[fromY][fromX] = EmptySquare;
8472       board[toY][toX] = EmptySquare;
8473       if((toX > fromX) != (piece == WhiteRook)) {
8474         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8475       } else {
8476         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8477       }
8478     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8479                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8480       board[fromY][fromX] = EmptySquare;
8481       board[toY][toX] = EmptySquare;
8482       if((toX > fromX) != (piece == BlackRook)) {
8483         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8484       } else {
8485         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8486       }
8487     /* End of code added by Tord */
8488
8489     } else if (board[fromY][fromX] == king
8490         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8491         && toY == fromY && toX > fromX+1) {
8492         board[fromY][fromX] = EmptySquare;
8493         board[toY][toX] = king;
8494         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8495         board[fromY][BOARD_RGHT-1] = EmptySquare;
8496     } else if (board[fromY][fromX] == king
8497         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8498                && toY == fromY && toX < fromX-1) {
8499         board[fromY][fromX] = EmptySquare;
8500         board[toY][toX] = king;
8501         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8502         board[fromY][BOARD_LEFT] = EmptySquare;
8503     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8504                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8505                && toY >= BOARD_HEIGHT-promoRank
8506                ) {
8507         /* white pawn promotion */
8508         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8509         if (board[toY][toX] == EmptySquare) {
8510             board[toY][toX] = WhiteQueen;
8511         }
8512         if(gameInfo.variant==VariantBughouse ||
8513            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8514             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8515         board[fromY][fromX] = EmptySquare;
8516     } else if ((fromY == BOARD_HEIGHT-4)
8517                && (toX != fromX)
8518                && gameInfo.variant != VariantXiangqi
8519                && gameInfo.variant != VariantBerolina
8520                && (board[fromY][fromX] == WhitePawn)
8521                && (board[toY][toX] == EmptySquare)) {
8522         board[fromY][fromX] = EmptySquare;
8523         board[toY][toX] = WhitePawn;
8524         captured = board[toY - 1][toX];
8525         board[toY - 1][toX] = EmptySquare;
8526     } else if ((fromY == BOARD_HEIGHT-4)
8527                && (toX == fromX)
8528                && gameInfo.variant == VariantBerolina
8529                && (board[fromY][fromX] == WhitePawn)
8530                && (board[toY][toX] == EmptySquare)) {
8531         board[fromY][fromX] = EmptySquare;
8532         board[toY][toX] = WhitePawn;
8533         if(oldEP & EP_BEROLIN_A) {
8534                 captured = board[fromY][fromX-1];
8535                 board[fromY][fromX-1] = EmptySquare;
8536         }else{  captured = board[fromY][fromX+1];
8537                 board[fromY][fromX+1] = EmptySquare;
8538         }
8539     } else if (board[fromY][fromX] == king
8540         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8541                && toY == fromY && toX > fromX+1) {
8542         board[fromY][fromX] = EmptySquare;
8543         board[toY][toX] = king;
8544         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8545         board[fromY][BOARD_RGHT-1] = EmptySquare;
8546     } else if (board[fromY][fromX] == king
8547         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8548                && toY == fromY && toX < fromX-1) {
8549         board[fromY][fromX] = EmptySquare;
8550         board[toY][toX] = king;
8551         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8552         board[fromY][BOARD_LEFT] = EmptySquare;
8553     } else if (fromY == 7 && fromX == 3
8554                && board[fromY][fromX] == BlackKing
8555                && toY == 7 && toX == 5) {
8556         board[fromY][fromX] = EmptySquare;
8557         board[toY][toX] = BlackKing;
8558         board[fromY][7] = EmptySquare;
8559         board[toY][4] = BlackRook;
8560     } else if (fromY == 7 && fromX == 3
8561                && board[fromY][fromX] == BlackKing
8562                && toY == 7 && toX == 1) {
8563         board[fromY][fromX] = EmptySquare;
8564         board[toY][toX] = BlackKing;
8565         board[fromY][0] = EmptySquare;
8566         board[toY][2] = BlackRook;
8567     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8568                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8569                && toY < promoRank
8570                ) {
8571         /* black pawn promotion */
8572         board[toY][toX] = CharToPiece(ToLower(promoChar));
8573         if (board[toY][toX] == EmptySquare) {
8574             board[toY][toX] = BlackQueen;
8575         }
8576         if(gameInfo.variant==VariantBughouse ||
8577            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8578             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8579         board[fromY][fromX] = EmptySquare;
8580     } else if ((fromY == 3)
8581                && (toX != fromX)
8582                && gameInfo.variant != VariantXiangqi
8583                && gameInfo.variant != VariantBerolina
8584                && (board[fromY][fromX] == BlackPawn)
8585                && (board[toY][toX] == EmptySquare)) {
8586         board[fromY][fromX] = EmptySquare;
8587         board[toY][toX] = BlackPawn;
8588         captured = board[toY + 1][toX];
8589         board[toY + 1][toX] = EmptySquare;
8590     } else if ((fromY == 3)
8591                && (toX == fromX)
8592                && gameInfo.variant == VariantBerolina
8593                && (board[fromY][fromX] == BlackPawn)
8594                && (board[toY][toX] == EmptySquare)) {
8595         board[fromY][fromX] = EmptySquare;
8596         board[toY][toX] = BlackPawn;
8597         if(oldEP & EP_BEROLIN_A) {
8598                 captured = board[fromY][fromX-1];
8599                 board[fromY][fromX-1] = EmptySquare;
8600         }else{  captured = board[fromY][fromX+1];
8601                 board[fromY][fromX+1] = EmptySquare;
8602         }
8603     } else {
8604         board[toY][toX] = board[fromY][fromX];
8605         board[fromY][fromX] = EmptySquare;
8606     }
8607   }
8608
8609     if (gameInfo.holdingsWidth != 0) {
8610
8611       /* !!A lot more code needs to be written to support holdings  */
8612       /* [HGM] OK, so I have written it. Holdings are stored in the */
8613       /* penultimate board files, so they are automaticlly stored   */
8614       /* in the game history.                                       */
8615       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8616                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8617         /* Delete from holdings, by decreasing count */
8618         /* and erasing image if necessary            */
8619         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8620         if(p < (int) BlackPawn) { /* white drop */
8621              p -= (int)WhitePawn;
8622                  p = PieceToNumber((ChessSquare)p);
8623              if(p >= gameInfo.holdingsSize) p = 0;
8624              if(--board[p][BOARD_WIDTH-2] <= 0)
8625                   board[p][BOARD_WIDTH-1] = EmptySquare;
8626              if((int)board[p][BOARD_WIDTH-2] < 0)
8627                         board[p][BOARD_WIDTH-2] = 0;
8628         } else {                  /* black drop */
8629              p -= (int)BlackPawn;
8630                  p = PieceToNumber((ChessSquare)p);
8631              if(p >= gameInfo.holdingsSize) p = 0;
8632              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8633                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8634              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8635                         board[BOARD_HEIGHT-1-p][1] = 0;
8636         }
8637       }
8638       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8639           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8640         /* [HGM] holdings: Add to holdings, if holdings exist */
8641         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8642                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8643                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8644         }
8645         p = (int) captured;
8646         if (p >= (int) BlackPawn) {
8647           p -= (int)BlackPawn;
8648           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8649                   /* in Shogi restore piece to its original  first */
8650                   captured = (ChessSquare) (DEMOTED captured);
8651                   p = DEMOTED p;
8652           }
8653           p = PieceToNumber((ChessSquare)p);
8654           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8655           board[p][BOARD_WIDTH-2]++;
8656           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8657         } else {
8658           p -= (int)WhitePawn;
8659           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8660                   captured = (ChessSquare) (DEMOTED captured);
8661                   p = DEMOTED p;
8662           }
8663           p = PieceToNumber((ChessSquare)p);
8664           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8665           board[BOARD_HEIGHT-1-p][1]++;
8666           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8667         }
8668       }
8669     } else if (gameInfo.variant == VariantAtomic) {
8670       if (captured != EmptySquare) {
8671         int y, x;
8672         for (y = toY-1; y <= toY+1; y++) {
8673           for (x = toX-1; x <= toX+1; x++) {
8674             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8675                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8676               board[y][x] = EmptySquare;
8677             }
8678           }
8679         }
8680         board[toY][toX] = EmptySquare;
8681       }
8682     }
8683     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8684         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8685     } else
8686     if(promoChar == '+') {
8687         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8688         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8689     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8690         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8691     }
8692     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8693                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8694         // [HGM] superchess: take promotion piece out of holdings
8695         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8696         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8697             if(!--board[k][BOARD_WIDTH-2])
8698                 board[k][BOARD_WIDTH-1] = EmptySquare;
8699         } else {
8700             if(!--board[BOARD_HEIGHT-1-k][1])
8701                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8702         }
8703     }
8704
8705 }
8706
8707 /* Updates forwardMostMove */
8708 void
8709 MakeMove(fromX, fromY, toX, toY, promoChar)
8710      int fromX, fromY, toX, toY;
8711      int promoChar;
8712 {
8713 //    forwardMostMove++; // [HGM] bare: moved downstream
8714
8715     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8716         int timeLeft; static int lastLoadFlag=0; int king, piece;
8717         piece = boards[forwardMostMove][fromY][fromX];
8718         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8719         if(gameInfo.variant == VariantKnightmate)
8720             king += (int) WhiteUnicorn - (int) WhiteKing;
8721         if(forwardMostMove == 0) {
8722             if(blackPlaysFirst)
8723                 fprintf(serverMoves, "%s;", second.tidy);
8724             fprintf(serverMoves, "%s;", first.tidy);
8725             if(!blackPlaysFirst)
8726                 fprintf(serverMoves, "%s;", second.tidy);
8727         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8728         lastLoadFlag = loadFlag;
8729         // print base move
8730         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8731         // print castling suffix
8732         if( toY == fromY && piece == king ) {
8733             if(toX-fromX > 1)
8734                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8735             if(fromX-toX >1)
8736                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8737         }
8738         // e.p. suffix
8739         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8740              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8741              boards[forwardMostMove][toY][toX] == EmptySquare
8742              && fromX != toX && fromY != toY)
8743                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8744         // promotion suffix
8745         if(promoChar != NULLCHAR)
8746                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8747         if(!loadFlag) {
8748             fprintf(serverMoves, "/%d/%d",
8749                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8750             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8751             else                      timeLeft = blackTimeRemaining/1000;
8752             fprintf(serverMoves, "/%d", timeLeft);
8753         }
8754         fflush(serverMoves);
8755     }
8756
8757     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8758       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8759                         0, 1);
8760       return;
8761     }
8762     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8763     if (commentList[forwardMostMove+1] != NULL) {
8764         free(commentList[forwardMostMove+1]);
8765         commentList[forwardMostMove+1] = NULL;
8766     }
8767     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8768     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8769     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8770     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8771     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8772     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8773     gameInfo.result = GameUnfinished;
8774     if (gameInfo.resultDetails != NULL) {
8775         free(gameInfo.resultDetails);
8776         gameInfo.resultDetails = NULL;
8777     }
8778     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8779                               moveList[forwardMostMove - 1]);
8780     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8781                              PosFlags(forwardMostMove - 1),
8782                              fromY, fromX, toY, toX, promoChar,
8783                              parseList[forwardMostMove - 1]);
8784     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8785       case MT_NONE:
8786       case MT_STALEMATE:
8787       default:
8788         break;
8789       case MT_CHECK:
8790         if(gameInfo.variant != VariantShogi)
8791             strcat(parseList[forwardMostMove - 1], "+");
8792         break;
8793       case MT_CHECKMATE:
8794       case MT_STAINMATE:
8795         strcat(parseList[forwardMostMove - 1], "#");
8796         break;
8797     }
8798     if (appData.debugMode) {
8799         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8800     }
8801
8802 }
8803
8804 /* Updates currentMove if not pausing */
8805 void
8806 ShowMove(fromX, fromY, toX, toY)
8807 {
8808     int instant = (gameMode == PlayFromGameFile) ?
8809         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8810     if(appData.noGUI) return;
8811     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8812         if (!instant) {
8813             if (forwardMostMove == currentMove + 1) {
8814                 AnimateMove(boards[forwardMostMove - 1],
8815                             fromX, fromY, toX, toY);
8816             }
8817             if (appData.highlightLastMove) {
8818                 SetHighlights(fromX, fromY, toX, toY);
8819             }
8820         }
8821         currentMove = forwardMostMove;
8822     }
8823
8824     if (instant) return;
8825
8826     DisplayMove(currentMove - 1);
8827     DrawPosition(FALSE, boards[currentMove]);
8828     DisplayBothClocks();
8829     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8830 }
8831
8832 void SendEgtPath(ChessProgramState *cps)
8833 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8834         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8835
8836         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8837
8838         while(*p) {
8839             char c, *q = name+1, *r, *s;
8840
8841             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8842             while(*p && *p != ',') *q++ = *p++;
8843             *q++ = ':'; *q = 0;
8844             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8845                 strcmp(name, ",nalimov:") == 0 ) {
8846                 // take nalimov path from the menu-changeable option first, if it is defined
8847               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8848                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8849             } else
8850             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8851                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8852                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8853                 s = r = StrStr(s, ":") + 1; // beginning of path info
8854                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8855                 c = *r; *r = 0;             // temporarily null-terminate path info
8856                     *--q = 0;               // strip of trailig ':' from name
8857                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8858                 *r = c;
8859                 SendToProgram(buf,cps);     // send egtbpath command for this format
8860             }
8861             if(*p == ',') p++; // read away comma to position for next format name
8862         }
8863 }
8864
8865 void
8866 InitChessProgram(cps, setup)
8867      ChessProgramState *cps;
8868      int setup; /* [HGM] needed to setup FRC opening position */
8869 {
8870     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8871     if (appData.noChessProgram) return;
8872     hintRequested = FALSE;
8873     bookRequested = FALSE;
8874
8875     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8876     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8877     if(cps->memSize) { /* [HGM] memory */
8878       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8879         SendToProgram(buf, cps);
8880     }
8881     SendEgtPath(cps); /* [HGM] EGT */
8882     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8883       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8884         SendToProgram(buf, cps);
8885     }
8886
8887     SendToProgram(cps->initString, cps);
8888     if (gameInfo.variant != VariantNormal &&
8889         gameInfo.variant != VariantLoadable
8890         /* [HGM] also send variant if board size non-standard */
8891         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8892                                             ) {
8893       char *v = VariantName(gameInfo.variant);
8894       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8895         /* [HGM] in protocol 1 we have to assume all variants valid */
8896         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8897         DisplayFatalError(buf, 0, 1);
8898         return;
8899       }
8900
8901       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8902       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8903       if( gameInfo.variant == VariantXiangqi )
8904            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8905       if( gameInfo.variant == VariantShogi )
8906            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8907       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8908            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8909       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8910                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8911            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8912       if( gameInfo.variant == VariantCourier )
8913            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8914       if( gameInfo.variant == VariantSuper )
8915            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8916       if( gameInfo.variant == VariantGreat )
8917            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8918       if( gameInfo.variant == VariantSChess )
8919            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8920
8921       if(overruled) {
8922         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8923                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8924            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8925            if(StrStr(cps->variants, b) == NULL) {
8926                // specific sized variant not known, check if general sizing allowed
8927                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8928                    if(StrStr(cps->variants, "boardsize") == NULL) {
8929                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8930                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8931                        DisplayFatalError(buf, 0, 1);
8932                        return;
8933                    }
8934                    /* [HGM] here we really should compare with the maximum supported board size */
8935                }
8936            }
8937       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8938       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8939       SendToProgram(buf, cps);
8940     }
8941     currentlyInitializedVariant = gameInfo.variant;
8942
8943     /* [HGM] send opening position in FRC to first engine */
8944     if(setup) {
8945           SendToProgram("force\n", cps);
8946           SendBoard(cps, 0);
8947           /* engine is now in force mode! Set flag to wake it up after first move. */
8948           setboardSpoiledMachineBlack = 1;
8949     }
8950
8951     if (cps->sendICS) {
8952       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8953       SendToProgram(buf, cps);
8954     }
8955     cps->maybeThinking = FALSE;
8956     cps->offeredDraw = 0;
8957     if (!appData.icsActive) {
8958         SendTimeControl(cps, movesPerSession, timeControl,
8959                         timeIncrement, appData.searchDepth,
8960                         searchTime);
8961     }
8962     if (appData.showThinking
8963         // [HGM] thinking: four options require thinking output to be sent
8964         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8965                                 ) {
8966         SendToProgram("post\n", cps);
8967     }
8968     SendToProgram("hard\n", cps);
8969     if (!appData.ponderNextMove) {
8970         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8971            it without being sure what state we are in first.  "hard"
8972            is not a toggle, so that one is OK.
8973          */
8974         SendToProgram("easy\n", cps);
8975     }
8976     if (cps->usePing) {
8977       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8978       SendToProgram(buf, cps);
8979     }
8980     cps->initDone = TRUE;
8981 }
8982
8983
8984 void
8985 StartChessProgram(cps)
8986      ChessProgramState *cps;
8987 {
8988     char buf[MSG_SIZ];
8989     int err;
8990
8991     if (appData.noChessProgram) return;
8992     cps->initDone = FALSE;
8993
8994     if (strcmp(cps->host, "localhost") == 0) {
8995         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8996     } else if (*appData.remoteShell == NULLCHAR) {
8997         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8998     } else {
8999         if (*appData.remoteUser == NULLCHAR) {
9000           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9001                     cps->program);
9002         } else {
9003           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9004                     cps->host, appData.remoteUser, cps->program);
9005         }
9006         err = StartChildProcess(buf, "", &cps->pr);
9007     }
9008
9009     if (err != 0) {
9010       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9011         DisplayFatalError(buf, err, 1);
9012         cps->pr = NoProc;
9013         cps->isr = NULL;
9014         return;
9015     }
9016
9017     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9018     if (cps->protocolVersion > 1) {
9019       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9020       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9021       cps->comboCnt = 0;  //                and values of combo boxes
9022       SendToProgram(buf, cps);
9023     } else {
9024       SendToProgram("xboard\n", cps);
9025     }
9026 }
9027
9028
9029 void
9030 TwoMachinesEventIfReady P((void))
9031 {
9032   if (first.lastPing != first.lastPong) {
9033     DisplayMessage("", _("Waiting for first chess program"));
9034     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9035     return;
9036   }
9037   if (second.lastPing != second.lastPong) {
9038     DisplayMessage("", _("Waiting for second chess program"));
9039     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9040     return;
9041   }
9042   ThawUI();
9043   TwoMachinesEvent();
9044 }
9045
9046 void
9047 NextMatchGame P((void))
9048 {
9049     int index; /* [HGM] autoinc: step load index during match */
9050     Reset(FALSE, TRUE);
9051     if (*appData.loadGameFile != NULLCHAR) {
9052         index = appData.loadGameIndex;
9053         if(index < 0) { // [HGM] autoinc
9054             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9055             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9056         }
9057         LoadGameFromFile(appData.loadGameFile,
9058                          index,
9059                          appData.loadGameFile, FALSE);
9060     } else if (*appData.loadPositionFile != NULLCHAR) {
9061         index = appData.loadPositionIndex;
9062         if(index < 0) { // [HGM] autoinc
9063             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9064             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9065         }
9066         LoadPositionFromFile(appData.loadPositionFile,
9067                              index,
9068                              appData.loadPositionFile);
9069     }
9070     TwoMachinesEventIfReady();
9071 }
9072
9073 void UserAdjudicationEvent( int result )
9074 {
9075     ChessMove gameResult = GameIsDrawn;
9076
9077     if( result > 0 ) {
9078         gameResult = WhiteWins;
9079     }
9080     else if( result < 0 ) {
9081         gameResult = BlackWins;
9082     }
9083
9084     if( gameMode == TwoMachinesPlay ) {
9085         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9086     }
9087 }
9088
9089
9090 // [HGM] save: calculate checksum of game to make games easily identifiable
9091 int StringCheckSum(char *s)
9092 {
9093         int i = 0;
9094         if(s==NULL) return 0;
9095         while(*s) i = i*259 + *s++;
9096         return i;
9097 }
9098
9099 int GameCheckSum()
9100 {
9101         int i, sum=0;
9102         for(i=backwardMostMove; i<forwardMostMove; i++) {
9103                 sum += pvInfoList[i].depth;
9104                 sum += StringCheckSum(parseList[i]);
9105                 sum += StringCheckSum(commentList[i]);
9106                 sum *= 261;
9107         }
9108         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9109         return sum + StringCheckSum(commentList[i]);
9110 } // end of save patch
9111
9112 void
9113 GameEnds(result, resultDetails, whosays)
9114      ChessMove result;
9115      char *resultDetails;
9116      int whosays;
9117 {
9118     GameMode nextGameMode;
9119     int isIcsGame;
9120     char buf[MSG_SIZ], popupRequested = 0;
9121
9122     if(endingGame) return; /* [HGM] crash: forbid recursion */
9123     endingGame = 1;
9124     if(twoBoards) { // [HGM] dual: switch back to one board
9125         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9126         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9127     }
9128     if (appData.debugMode) {
9129       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9130               result, resultDetails ? resultDetails : "(null)", whosays);
9131     }
9132
9133     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9134
9135     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9136         /* If we are playing on ICS, the server decides when the
9137            game is over, but the engine can offer to draw, claim
9138            a draw, or resign.
9139          */
9140 #if ZIPPY
9141         if (appData.zippyPlay && first.initDone) {
9142             if (result == GameIsDrawn) {
9143                 /* In case draw still needs to be claimed */
9144                 SendToICS(ics_prefix);
9145                 SendToICS("draw\n");
9146             } else if (StrCaseStr(resultDetails, "resign")) {
9147                 SendToICS(ics_prefix);
9148                 SendToICS("resign\n");
9149             }
9150         }
9151 #endif
9152         endingGame = 0; /* [HGM] crash */
9153         return;
9154     }
9155
9156     /* If we're loading the game from a file, stop */
9157     if (whosays == GE_FILE) {
9158       (void) StopLoadGameTimer();
9159       gameFileFP = NULL;
9160     }
9161
9162     /* Cancel draw offers */
9163     first.offeredDraw = second.offeredDraw = 0;
9164
9165     /* If this is an ICS game, only ICS can really say it's done;
9166        if not, anyone can. */
9167     isIcsGame = (gameMode == IcsPlayingWhite ||
9168                  gameMode == IcsPlayingBlack ||
9169                  gameMode == IcsObserving    ||
9170                  gameMode == IcsExamining);
9171
9172     if (!isIcsGame || whosays == GE_ICS) {
9173         /* OK -- not an ICS game, or ICS said it was done */
9174         StopClocks();
9175         if (!isIcsGame && !appData.noChessProgram)
9176           SetUserThinkingEnables();
9177
9178         /* [HGM] if a machine claims the game end we verify this claim */
9179         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9180             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9181                 char claimer;
9182                 ChessMove trueResult = (ChessMove) -1;
9183
9184                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9185                                             first.twoMachinesColor[0] :
9186                                             second.twoMachinesColor[0] ;
9187
9188                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9189                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9190                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9191                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9192                 } else
9193                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9194                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9195                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9196                 } else
9197                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9198                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9199                 }
9200
9201                 // now verify win claims, but not in drop games, as we don't understand those yet
9202                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9203                                                  || gameInfo.variant == VariantGreat) &&
9204                     (result == WhiteWins && claimer == 'w' ||
9205                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9206                       if (appData.debugMode) {
9207                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9208                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9209                       }
9210                       if(result != trueResult) {
9211                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9212                               result = claimer == 'w' ? BlackWins : WhiteWins;
9213                               resultDetails = buf;
9214                       }
9215                 } else
9216                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9217                     && (forwardMostMove <= backwardMostMove ||
9218                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9219                         (claimer=='b')==(forwardMostMove&1))
9220                                                                                   ) {
9221                       /* [HGM] verify: draws that were not flagged are false claims */
9222                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9223                       result = claimer == 'w' ? BlackWins : WhiteWins;
9224                       resultDetails = buf;
9225                 }
9226                 /* (Claiming a loss is accepted no questions asked!) */
9227             }
9228             /* [HGM] bare: don't allow bare King to win */
9229             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9230                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9231                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9232                && result != GameIsDrawn)
9233             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9234                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9235                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9236                         if(p >= 0 && p <= (int)WhiteKing) k++;
9237                 }
9238                 if (appData.debugMode) {
9239                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9240                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9241                 }
9242                 if(k <= 1) {
9243                         result = GameIsDrawn;
9244                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9245                         resultDetails = buf;
9246                 }
9247             }
9248         }
9249
9250
9251         if(serverMoves != NULL && !loadFlag) { char c = '=';
9252             if(result==WhiteWins) c = '+';
9253             if(result==BlackWins) c = '-';
9254             if(resultDetails != NULL)
9255                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9256         }
9257         if (resultDetails != NULL) {
9258             gameInfo.result = result;
9259             gameInfo.resultDetails = StrSave(resultDetails);
9260
9261             /* display last move only if game was not loaded from file */
9262             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9263                 DisplayMove(currentMove - 1);
9264
9265             if (forwardMostMove != 0) {
9266                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9267                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9268                                                                 ) {
9269                     if (*appData.saveGameFile != NULLCHAR) {
9270                         SaveGameToFile(appData.saveGameFile, TRUE);
9271                     } else if (appData.autoSaveGames) {
9272                         AutoSaveGame();
9273                     }
9274                     if (*appData.savePositionFile != NULLCHAR) {
9275                         SavePositionToFile(appData.savePositionFile);
9276                     }
9277                 }
9278             }
9279
9280             /* Tell program how game ended in case it is learning */
9281             /* [HGM] Moved this to after saving the PGN, just in case */
9282             /* engine died and we got here through time loss. In that */
9283             /* case we will get a fatal error writing the pipe, which */
9284             /* would otherwise lose us the PGN.                       */
9285             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9286             /* output during GameEnds should never be fatal anymore   */
9287             if (gameMode == MachinePlaysWhite ||
9288                 gameMode == MachinePlaysBlack ||
9289                 gameMode == TwoMachinesPlay ||
9290                 gameMode == IcsPlayingWhite ||
9291                 gameMode == IcsPlayingBlack ||
9292                 gameMode == BeginningOfGame) {
9293                 char buf[MSG_SIZ];
9294                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9295                         resultDetails);
9296                 if (first.pr != NoProc) {
9297                     SendToProgram(buf, &first);
9298                 }
9299                 if (second.pr != NoProc &&
9300                     gameMode == TwoMachinesPlay) {
9301                     SendToProgram(buf, &second);
9302                 }
9303             }
9304         }
9305
9306         if (appData.icsActive) {
9307             if (appData.quietPlay &&
9308                 (gameMode == IcsPlayingWhite ||
9309                  gameMode == IcsPlayingBlack)) {
9310                 SendToICS(ics_prefix);
9311                 SendToICS("set shout 1\n");
9312             }
9313             nextGameMode = IcsIdle;
9314             ics_user_moved = FALSE;
9315             /* clean up premove.  It's ugly when the game has ended and the
9316              * premove highlights are still on the board.
9317              */
9318             if (gotPremove) {
9319               gotPremove = FALSE;
9320               ClearPremoveHighlights();
9321               DrawPosition(FALSE, boards[currentMove]);
9322             }
9323             if (whosays == GE_ICS) {
9324                 switch (result) {
9325                 case WhiteWins:
9326                     if (gameMode == IcsPlayingWhite)
9327                         PlayIcsWinSound();
9328                     else if(gameMode == IcsPlayingBlack)
9329                         PlayIcsLossSound();
9330                     break;
9331                 case BlackWins:
9332                     if (gameMode == IcsPlayingBlack)
9333                         PlayIcsWinSound();
9334                     else if(gameMode == IcsPlayingWhite)
9335                         PlayIcsLossSound();
9336                     break;
9337                 case GameIsDrawn:
9338                     PlayIcsDrawSound();
9339                     break;
9340                 default:
9341                     PlayIcsUnfinishedSound();
9342                 }
9343             }
9344         } else if (gameMode == EditGame ||
9345                    gameMode == PlayFromGameFile ||
9346                    gameMode == AnalyzeMode ||
9347                    gameMode == AnalyzeFile) {
9348             nextGameMode = gameMode;
9349         } else {
9350             nextGameMode = EndOfGame;
9351         }
9352         pausing = FALSE;
9353         ModeHighlight();
9354     } else {
9355         nextGameMode = gameMode;
9356     }
9357
9358     if (appData.noChessProgram) {
9359         gameMode = nextGameMode;
9360         ModeHighlight();
9361         endingGame = 0; /* [HGM] crash */
9362         return;
9363     }
9364
9365     if (first.reuse) {
9366         /* Put first chess program into idle state */
9367         if (first.pr != NoProc &&
9368             (gameMode == MachinePlaysWhite ||
9369              gameMode == MachinePlaysBlack ||
9370              gameMode == TwoMachinesPlay ||
9371              gameMode == IcsPlayingWhite ||
9372              gameMode == IcsPlayingBlack ||
9373              gameMode == BeginningOfGame)) {
9374             SendToProgram("force\n", &first);
9375             if (first.usePing) {
9376               char buf[MSG_SIZ];
9377               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9378               SendToProgram(buf, &first);
9379             }
9380         }
9381     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9382         /* Kill off first chess program */
9383         if (first.isr != NULL)
9384           RemoveInputSource(first.isr);
9385         first.isr = NULL;
9386
9387         if (first.pr != NoProc) {
9388             ExitAnalyzeMode();
9389             DoSleep( appData.delayBeforeQuit );
9390             SendToProgram("quit\n", &first);
9391             DoSleep( appData.delayAfterQuit );
9392             DestroyChildProcess(first.pr, first.useSigterm);
9393         }
9394         first.pr = NoProc;
9395     }
9396     if (second.reuse) {
9397         /* Put second chess program into idle state */
9398         if (second.pr != NoProc &&
9399             gameMode == TwoMachinesPlay) {
9400             SendToProgram("force\n", &second);
9401             if (second.usePing) {
9402               char buf[MSG_SIZ];
9403               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9404               SendToProgram(buf, &second);
9405             }
9406         }
9407     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9408         /* Kill off second chess program */
9409         if (second.isr != NULL)
9410           RemoveInputSource(second.isr);
9411         second.isr = NULL;
9412
9413         if (second.pr != NoProc) {
9414             DoSleep( appData.delayBeforeQuit );
9415             SendToProgram("quit\n", &second);
9416             DoSleep( appData.delayAfterQuit );
9417             DestroyChildProcess(second.pr, second.useSigterm);
9418         }
9419         second.pr = NoProc;
9420     }
9421
9422     if (matchMode && gameMode == TwoMachinesPlay) {
9423         switch (result) {
9424         case WhiteWins:
9425           if (first.twoMachinesColor[0] == 'w') {
9426             first.matchWins++;
9427           } else {
9428             second.matchWins++;
9429           }
9430           break;
9431         case BlackWins:
9432           if (first.twoMachinesColor[0] == 'b') {
9433             first.matchWins++;
9434           } else {
9435             second.matchWins++;
9436           }
9437           break;
9438         default:
9439           break;
9440         }
9441         if (matchGame < appData.matchGames) {
9442             char *tmp;
9443             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9444                 tmp = first.twoMachinesColor;
9445                 first.twoMachinesColor = second.twoMachinesColor;
9446                 second.twoMachinesColor = tmp;
9447             }
9448             gameMode = nextGameMode;
9449             matchGame++;
9450             if(appData.matchPause>10000 || appData.matchPause<10)
9451                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9452             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9453             endingGame = 0; /* [HGM] crash */
9454             return;
9455         } else {
9456             gameMode = nextGameMode;
9457             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9458                      first.tidy, second.tidy,
9459                      first.matchWins, second.matchWins,
9460                      appData.matchGames - (first.matchWins + second.matchWins));
9461             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9462             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9463                 first.twoMachinesColor = "black\n";
9464                 second.twoMachinesColor = "white\n";
9465             } else {
9466                 first.twoMachinesColor = "white\n";
9467                 second.twoMachinesColor = "black\n";
9468             }
9469         }
9470     }
9471     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9472         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9473       ExitAnalyzeMode();
9474     gameMode = nextGameMode;
9475     ModeHighlight();
9476     endingGame = 0;  /* [HGM] crash */
9477     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9478       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9479         matchMode = FALSE; appData.matchGames = matchGame = 0;
9480         DisplayNote(buf);
9481       }
9482     }
9483 }
9484
9485 /* Assumes program was just initialized (initString sent).
9486    Leaves program in force mode. */
9487 void
9488 FeedMovesToProgram(cps, upto)
9489      ChessProgramState *cps;
9490      int upto;
9491 {
9492     int i;
9493
9494     if (appData.debugMode)
9495       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9496               startedFromSetupPosition ? "position and " : "",
9497               backwardMostMove, upto, cps->which);
9498     if(currentlyInitializedVariant != gameInfo.variant) {
9499       char buf[MSG_SIZ];
9500         // [HGM] variantswitch: make engine aware of new variant
9501         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9502                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9503         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9504         SendToProgram(buf, cps);
9505         currentlyInitializedVariant = gameInfo.variant;
9506     }
9507     SendToProgram("force\n", cps);
9508     if (startedFromSetupPosition) {
9509         SendBoard(cps, backwardMostMove);
9510     if (appData.debugMode) {
9511         fprintf(debugFP, "feedMoves\n");
9512     }
9513     }
9514     for (i = backwardMostMove; i < upto; i++) {
9515         SendMoveToProgram(i, cps);
9516     }
9517 }
9518
9519
9520 void
9521 ResurrectChessProgram()
9522 {
9523      /* The chess program may have exited.
9524         If so, restart it and feed it all the moves made so far. */
9525
9526     if (appData.noChessProgram || first.pr != NoProc) return;
9527
9528     StartChessProgram(&first);
9529     InitChessProgram(&first, FALSE);
9530     FeedMovesToProgram(&first, currentMove);
9531
9532     if (!first.sendTime) {
9533         /* can't tell gnuchess what its clock should read,
9534            so we bow to its notion. */
9535         ResetClocks();
9536         timeRemaining[0][currentMove] = whiteTimeRemaining;
9537         timeRemaining[1][currentMove] = blackTimeRemaining;
9538     }
9539
9540     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9541                 appData.icsEngineAnalyze) && first.analysisSupport) {
9542       SendToProgram("analyze\n", &first);
9543       first.analyzing = TRUE;
9544     }
9545 }
9546
9547 /*
9548  * Button procedures
9549  */
9550 void
9551 Reset(redraw, init)
9552      int redraw, init;
9553 {
9554     int i;
9555
9556     if (appData.debugMode) {
9557         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9558                 redraw, init, gameMode);
9559     }
9560     CleanupTail(); // [HGM] vari: delete any stored variations
9561     pausing = pauseExamInvalid = FALSE;
9562     startedFromSetupPosition = blackPlaysFirst = FALSE;
9563     firstMove = TRUE;
9564     whiteFlag = blackFlag = FALSE;
9565     userOfferedDraw = FALSE;
9566     hintRequested = bookRequested = FALSE;
9567     first.maybeThinking = FALSE;
9568     second.maybeThinking = FALSE;
9569     first.bookSuspend = FALSE; // [HGM] book
9570     second.bookSuspend = FALSE;
9571     thinkOutput[0] = NULLCHAR;
9572     lastHint[0] = NULLCHAR;
9573     ClearGameInfo(&gameInfo);
9574     gameInfo.variant = StringToVariant(appData.variant);
9575     ics_user_moved = ics_clock_paused = FALSE;
9576     ics_getting_history = H_FALSE;
9577     ics_gamenum = -1;
9578     white_holding[0] = black_holding[0] = NULLCHAR;
9579     ClearProgramStats();
9580     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9581
9582     ResetFrontEnd();
9583     ClearHighlights();
9584     flipView = appData.flipView;
9585     ClearPremoveHighlights();
9586     gotPremove = FALSE;
9587     alarmSounded = FALSE;
9588
9589     GameEnds(EndOfFile, NULL, GE_PLAYER);
9590     if(appData.serverMovesName != NULL) {
9591         /* [HGM] prepare to make moves file for broadcasting */
9592         clock_t t = clock();
9593         if(serverMoves != NULL) fclose(serverMoves);
9594         serverMoves = fopen(appData.serverMovesName, "r");
9595         if(serverMoves != NULL) {
9596             fclose(serverMoves);
9597             /* delay 15 sec before overwriting, so all clients can see end */
9598             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9599         }
9600         serverMoves = fopen(appData.serverMovesName, "w");
9601     }
9602
9603     ExitAnalyzeMode();
9604     gameMode = BeginningOfGame;
9605     ModeHighlight();
9606     if(appData.icsActive) gameInfo.variant = VariantNormal;
9607     currentMove = forwardMostMove = backwardMostMove = 0;
9608     InitPosition(redraw);
9609     for (i = 0; i < MAX_MOVES; i++) {
9610         if (commentList[i] != NULL) {
9611             free(commentList[i]);
9612             commentList[i] = NULL;
9613         }
9614     }
9615     ResetClocks();
9616     timeRemaining[0][0] = whiteTimeRemaining;
9617     timeRemaining[1][0] = blackTimeRemaining;
9618     if (first.pr == NULL) {
9619         StartChessProgram(&first);
9620     }
9621     if (init) {
9622             InitChessProgram(&first, startedFromSetupPosition);
9623     }
9624     DisplayTitle("");
9625     DisplayMessage("", "");
9626     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9627     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9628 }
9629
9630 void
9631 AutoPlayGameLoop()
9632 {
9633     for (;;) {
9634         if (!AutoPlayOneMove())
9635           return;
9636         if (matchMode || appData.timeDelay == 0)
9637           continue;
9638         if (appData.timeDelay < 0)
9639           return;
9640         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9641         break;
9642     }
9643 }
9644
9645
9646 int
9647 AutoPlayOneMove()
9648 {
9649     int fromX, fromY, toX, toY;
9650
9651     if (appData.debugMode) {
9652       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9653     }
9654
9655     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9656       return FALSE;
9657
9658     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9659       pvInfoList[currentMove].depth = programStats.depth;
9660       pvInfoList[currentMove].score = programStats.score;
9661       pvInfoList[currentMove].time  = 0;
9662       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9663     }
9664
9665     if (currentMove >= forwardMostMove) {
9666       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9667       gameMode = EditGame;
9668       ModeHighlight();
9669
9670       /* [AS] Clear current move marker at the end of a game */
9671       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9672
9673       return FALSE;
9674     }
9675
9676     toX = moveList[currentMove][2] - AAA;
9677     toY = moveList[currentMove][3] - ONE;
9678
9679     if (moveList[currentMove][1] == '@') {
9680         if (appData.highlightLastMove) {
9681             SetHighlights(-1, -1, toX, toY);
9682         }
9683     } else {
9684         fromX = moveList[currentMove][0] - AAA;
9685         fromY = moveList[currentMove][1] - ONE;
9686
9687         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9688
9689         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9690
9691         if (appData.highlightLastMove) {
9692             SetHighlights(fromX, fromY, toX, toY);
9693         }
9694     }
9695     DisplayMove(currentMove);
9696     SendMoveToProgram(currentMove++, &first);
9697     DisplayBothClocks();
9698     DrawPosition(FALSE, boards[currentMove]);
9699     // [HGM] PV info: always display, routine tests if empty
9700     DisplayComment(currentMove - 1, commentList[currentMove]);
9701     return TRUE;
9702 }
9703
9704
9705 int
9706 LoadGameOneMove(readAhead)
9707      ChessMove readAhead;
9708 {
9709     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9710     char promoChar = NULLCHAR;
9711     ChessMove moveType;
9712     char move[MSG_SIZ];
9713     char *p, *q;
9714
9715     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9716         gameMode != AnalyzeMode && gameMode != Training) {
9717         gameFileFP = NULL;
9718         return FALSE;
9719     }
9720
9721     yyboardindex = forwardMostMove;
9722     if (readAhead != EndOfFile) {
9723       moveType = readAhead;
9724     } else {
9725       if (gameFileFP == NULL)
9726           return FALSE;
9727       moveType = (ChessMove) Myylex();
9728     }
9729
9730     done = FALSE;
9731     switch (moveType) {
9732       case Comment:
9733         if (appData.debugMode)
9734           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9735         p = yy_text;
9736
9737         /* append the comment but don't display it */
9738         AppendComment(currentMove, p, FALSE);
9739         return TRUE;
9740
9741       case WhiteCapturesEnPassant:
9742       case BlackCapturesEnPassant:
9743       case WhitePromotion:
9744       case BlackPromotion:
9745       case WhiteNonPromotion:
9746       case BlackNonPromotion:
9747       case NormalMove:
9748       case WhiteKingSideCastle:
9749       case WhiteQueenSideCastle:
9750       case BlackKingSideCastle:
9751       case BlackQueenSideCastle:
9752       case WhiteKingSideCastleWild:
9753       case WhiteQueenSideCastleWild:
9754       case BlackKingSideCastleWild:
9755       case BlackQueenSideCastleWild:
9756       /* PUSH Fabien */
9757       case WhiteHSideCastleFR:
9758       case WhiteASideCastleFR:
9759       case BlackHSideCastleFR:
9760       case BlackASideCastleFR:
9761       /* POP Fabien */
9762         if (appData.debugMode)
9763           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9764         fromX = currentMoveString[0] - AAA;
9765         fromY = currentMoveString[1] - ONE;
9766         toX = currentMoveString[2] - AAA;
9767         toY = currentMoveString[3] - ONE;
9768         promoChar = currentMoveString[4];
9769         break;
9770
9771       case WhiteDrop:
9772       case BlackDrop:
9773         if (appData.debugMode)
9774           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9775         fromX = moveType == WhiteDrop ?
9776           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9777         (int) CharToPiece(ToLower(currentMoveString[0]));
9778         fromY = DROP_RANK;
9779         toX = currentMoveString[2] - AAA;
9780         toY = currentMoveString[3] - ONE;
9781         break;
9782
9783       case WhiteWins:
9784       case BlackWins:
9785       case GameIsDrawn:
9786       case GameUnfinished:
9787         if (appData.debugMode)
9788           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9789         p = strchr(yy_text, '{');
9790         if (p == NULL) p = strchr(yy_text, '(');
9791         if (p == NULL) {
9792             p = yy_text;
9793             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9794         } else {
9795             q = strchr(p, *p == '{' ? '}' : ')');
9796             if (q != NULL) *q = NULLCHAR;
9797             p++;
9798         }
9799         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9800         GameEnds(moveType, p, GE_FILE);
9801         done = TRUE;
9802         if (cmailMsgLoaded) {
9803             ClearHighlights();
9804             flipView = WhiteOnMove(currentMove);
9805             if (moveType == GameUnfinished) flipView = !flipView;
9806             if (appData.debugMode)
9807               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9808         }
9809         break;
9810
9811       case EndOfFile:
9812         if (appData.debugMode)
9813           fprintf(debugFP, "Parser hit end of file\n");
9814         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9815           case MT_NONE:
9816           case MT_CHECK:
9817             break;
9818           case MT_CHECKMATE:
9819           case MT_STAINMATE:
9820             if (WhiteOnMove(currentMove)) {
9821                 GameEnds(BlackWins, "Black mates", GE_FILE);
9822             } else {
9823                 GameEnds(WhiteWins, "White mates", GE_FILE);
9824             }
9825             break;
9826           case MT_STALEMATE:
9827             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9828             break;
9829         }
9830         done = TRUE;
9831         break;
9832
9833       case MoveNumberOne:
9834         if (lastLoadGameStart == GNUChessGame) {
9835             /* GNUChessGames have numbers, but they aren't move numbers */
9836             if (appData.debugMode)
9837               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9838                       yy_text, (int) moveType);
9839             return LoadGameOneMove(EndOfFile); /* tail recursion */
9840         }
9841         /* else fall thru */
9842
9843       case XBoardGame:
9844       case GNUChessGame:
9845       case PGNTag:
9846         /* Reached start of next game in file */
9847         if (appData.debugMode)
9848           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9849         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9850           case MT_NONE:
9851           case MT_CHECK:
9852             break;
9853           case MT_CHECKMATE:
9854           case MT_STAINMATE:
9855             if (WhiteOnMove(currentMove)) {
9856                 GameEnds(BlackWins, "Black mates", GE_FILE);
9857             } else {
9858                 GameEnds(WhiteWins, "White mates", GE_FILE);
9859             }
9860             break;
9861           case MT_STALEMATE:
9862             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9863             break;
9864         }
9865         done = TRUE;
9866         break;
9867
9868       case PositionDiagram:     /* should not happen; ignore */
9869       case ElapsedTime:         /* ignore */
9870       case NAG:                 /* ignore */
9871         if (appData.debugMode)
9872           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9873                   yy_text, (int) moveType);
9874         return LoadGameOneMove(EndOfFile); /* tail recursion */
9875
9876       case IllegalMove:
9877         if (appData.testLegality) {
9878             if (appData.debugMode)
9879               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9880             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9881                     (forwardMostMove / 2) + 1,
9882                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9883             DisplayError(move, 0);
9884             done = TRUE;
9885         } else {
9886             if (appData.debugMode)
9887               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9888                       yy_text, currentMoveString);
9889             fromX = currentMoveString[0] - AAA;
9890             fromY = currentMoveString[1] - ONE;
9891             toX = currentMoveString[2] - AAA;
9892             toY = currentMoveString[3] - ONE;
9893             promoChar = currentMoveString[4];
9894         }
9895         break;
9896
9897       case AmbiguousMove:
9898         if (appData.debugMode)
9899           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9900         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9901                 (forwardMostMove / 2) + 1,
9902                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9903         DisplayError(move, 0);
9904         done = TRUE;
9905         break;
9906
9907       default:
9908       case ImpossibleMove:
9909         if (appData.debugMode)
9910           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9911         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9912                 (forwardMostMove / 2) + 1,
9913                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9914         DisplayError(move, 0);
9915         done = TRUE;
9916         break;
9917     }
9918
9919     if (done) {
9920         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9921             DrawPosition(FALSE, boards[currentMove]);
9922             DisplayBothClocks();
9923             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9924               DisplayComment(currentMove - 1, commentList[currentMove]);
9925         }
9926         (void) StopLoadGameTimer();
9927         gameFileFP = NULL;
9928         cmailOldMove = forwardMostMove;
9929         return FALSE;
9930     } else {
9931         /* currentMoveString is set as a side-effect of yylex */
9932         strcat(currentMoveString, "\n");
9933         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9934
9935         thinkOutput[0] = NULLCHAR;
9936         MakeMove(fromX, fromY, toX, toY, promoChar);
9937         currentMove = forwardMostMove;
9938         return TRUE;
9939     }
9940 }
9941
9942 /* Load the nth game from the given file */
9943 int
9944 LoadGameFromFile(filename, n, title, useList)
9945      char *filename;
9946      int n;
9947      char *title;
9948      /*Boolean*/ int useList;
9949 {
9950     FILE *f;
9951     char buf[MSG_SIZ];
9952
9953     if (strcmp(filename, "-") == 0) {
9954         f = stdin;
9955         title = "stdin";
9956     } else {
9957         f = fopen(filename, "rb");
9958         if (f == NULL) {
9959           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9960             DisplayError(buf, errno);
9961             return FALSE;
9962         }
9963     }
9964     if (fseek(f, 0, 0) == -1) {
9965         /* f is not seekable; probably a pipe */
9966         useList = FALSE;
9967     }
9968     if (useList && n == 0) {
9969         int error = GameListBuild(f);
9970         if (error) {
9971             DisplayError(_("Cannot build game list"), error);
9972         } else if (!ListEmpty(&gameList) &&
9973                    ((ListGame *) gameList.tailPred)->number > 1) {
9974             GameListPopUp(f, title);
9975             return TRUE;
9976         }
9977         GameListDestroy();
9978         n = 1;
9979     }
9980     if (n == 0) n = 1;
9981     return LoadGame(f, n, title, FALSE);
9982 }
9983
9984
9985 void
9986 MakeRegisteredMove()
9987 {
9988     int fromX, fromY, toX, toY;
9989     char promoChar;
9990     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9991         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9992           case CMAIL_MOVE:
9993           case CMAIL_DRAW:
9994             if (appData.debugMode)
9995               fprintf(debugFP, "Restoring %s for game %d\n",
9996                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9997
9998             thinkOutput[0] = NULLCHAR;
9999             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10000             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10001             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10002             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10003             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10004             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10005             MakeMove(fromX, fromY, toX, toY, promoChar);
10006             ShowMove(fromX, fromY, toX, toY);
10007
10008             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10009               case MT_NONE:
10010               case MT_CHECK:
10011                 break;
10012
10013               case MT_CHECKMATE:
10014               case MT_STAINMATE:
10015                 if (WhiteOnMove(currentMove)) {
10016                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10017                 } else {
10018                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10019                 }
10020                 break;
10021
10022               case MT_STALEMATE:
10023                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10024                 break;
10025             }
10026
10027             break;
10028
10029           case CMAIL_RESIGN:
10030             if (WhiteOnMove(currentMove)) {
10031                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10032             } else {
10033                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10034             }
10035             break;
10036
10037           case CMAIL_ACCEPT:
10038             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10039             break;
10040
10041           default:
10042             break;
10043         }
10044     }
10045
10046     return;
10047 }
10048
10049 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10050 int
10051 CmailLoadGame(f, gameNumber, title, useList)
10052      FILE *f;
10053      int gameNumber;
10054      char *title;
10055      int useList;
10056 {
10057     int retVal;
10058
10059     if (gameNumber > nCmailGames) {
10060         DisplayError(_("No more games in this message"), 0);
10061         return FALSE;
10062     }
10063     if (f == lastLoadGameFP) {
10064         int offset = gameNumber - lastLoadGameNumber;
10065         if (offset == 0) {
10066             cmailMsg[0] = NULLCHAR;
10067             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10068                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10069                 nCmailMovesRegistered--;
10070             }
10071             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10072             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10073                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10074             }
10075         } else {
10076             if (! RegisterMove()) return FALSE;
10077         }
10078     }
10079
10080     retVal = LoadGame(f, gameNumber, title, useList);
10081
10082     /* Make move registered during previous look at this game, if any */
10083     MakeRegisteredMove();
10084
10085     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10086         commentList[currentMove]
10087           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10088         DisplayComment(currentMove - 1, commentList[currentMove]);
10089     }
10090
10091     return retVal;
10092 }
10093
10094 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10095 int
10096 ReloadGame(offset)
10097      int offset;
10098 {
10099     int gameNumber = lastLoadGameNumber + offset;
10100     if (lastLoadGameFP == NULL) {
10101         DisplayError(_("No game has been loaded yet"), 0);
10102         return FALSE;
10103     }
10104     if (gameNumber <= 0) {
10105         DisplayError(_("Can't back up any further"), 0);
10106         return FALSE;
10107     }
10108     if (cmailMsgLoaded) {
10109         return CmailLoadGame(lastLoadGameFP, gameNumber,
10110                              lastLoadGameTitle, lastLoadGameUseList);
10111     } else {
10112         return LoadGame(lastLoadGameFP, gameNumber,
10113                         lastLoadGameTitle, lastLoadGameUseList);
10114     }
10115 }
10116
10117
10118
10119 /* Load the nth game from open file f */
10120 int
10121 LoadGame(f, gameNumber, title, useList)
10122      FILE *f;
10123      int gameNumber;
10124      char *title;
10125      int useList;
10126 {
10127     ChessMove cm;
10128     char buf[MSG_SIZ];
10129     int gn = gameNumber;
10130     ListGame *lg = NULL;
10131     int numPGNTags = 0;
10132     int err;
10133     GameMode oldGameMode;
10134     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10135
10136     if (appData.debugMode)
10137         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10138
10139     if (gameMode == Training )
10140         SetTrainingModeOff();
10141
10142     oldGameMode = gameMode;
10143     if (gameMode != BeginningOfGame) {
10144       Reset(FALSE, TRUE);
10145     }
10146
10147     gameFileFP = f;
10148     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10149         fclose(lastLoadGameFP);
10150     }
10151
10152     if (useList) {
10153         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10154
10155         if (lg) {
10156             fseek(f, lg->offset, 0);
10157             GameListHighlight(gameNumber);
10158             gn = 1;
10159         }
10160         else {
10161             DisplayError(_("Game number out of range"), 0);
10162             return FALSE;
10163         }
10164     } else {
10165         GameListDestroy();
10166         if (fseek(f, 0, 0) == -1) {
10167             if (f == lastLoadGameFP ?
10168                 gameNumber == lastLoadGameNumber + 1 :
10169                 gameNumber == 1) {
10170                 gn = 1;
10171             } else {
10172                 DisplayError(_("Can't seek on game file"), 0);
10173                 return FALSE;
10174             }
10175         }
10176     }
10177     lastLoadGameFP = f;
10178     lastLoadGameNumber = gameNumber;
10179     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10180     lastLoadGameUseList = useList;
10181
10182     yynewfile(f);
10183
10184     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10185       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10186                 lg->gameInfo.black);
10187             DisplayTitle(buf);
10188     } else if (*title != NULLCHAR) {
10189         if (gameNumber > 1) {
10190           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10191             DisplayTitle(buf);
10192         } else {
10193             DisplayTitle(title);
10194         }
10195     }
10196
10197     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10198         gameMode = PlayFromGameFile;
10199         ModeHighlight();
10200     }
10201
10202     currentMove = forwardMostMove = backwardMostMove = 0;
10203     CopyBoard(boards[0], initialPosition);
10204     StopClocks();
10205
10206     /*
10207      * Skip the first gn-1 games in the file.
10208      * Also skip over anything that precedes an identifiable
10209      * start of game marker, to avoid being confused by
10210      * garbage at the start of the file.  Currently
10211      * recognized start of game markers are the move number "1",
10212      * the pattern "gnuchess .* game", the pattern
10213      * "^[#;%] [^ ]* game file", and a PGN tag block.
10214      * A game that starts with one of the latter two patterns
10215      * will also have a move number 1, possibly
10216      * following a position diagram.
10217      * 5-4-02: Let's try being more lenient and allowing a game to
10218      * start with an unnumbered move.  Does that break anything?
10219      */
10220     cm = lastLoadGameStart = EndOfFile;
10221     while (gn > 0) {
10222         yyboardindex = forwardMostMove;
10223         cm = (ChessMove) Myylex();
10224         switch (cm) {
10225           case EndOfFile:
10226             if (cmailMsgLoaded) {
10227                 nCmailGames = CMAIL_MAX_GAMES - gn;
10228             } else {
10229                 Reset(TRUE, TRUE);
10230                 DisplayError(_("Game not found in file"), 0);
10231             }
10232             return FALSE;
10233
10234           case GNUChessGame:
10235           case XBoardGame:
10236             gn--;
10237             lastLoadGameStart = cm;
10238             break;
10239
10240           case MoveNumberOne:
10241             switch (lastLoadGameStart) {
10242               case GNUChessGame:
10243               case XBoardGame:
10244               case PGNTag:
10245                 break;
10246               case MoveNumberOne:
10247               case EndOfFile:
10248                 gn--;           /* count this game */
10249                 lastLoadGameStart = cm;
10250                 break;
10251               default:
10252                 /* impossible */
10253                 break;
10254             }
10255             break;
10256
10257           case PGNTag:
10258             switch (lastLoadGameStart) {
10259               case GNUChessGame:
10260               case PGNTag:
10261               case MoveNumberOne:
10262               case EndOfFile:
10263                 gn--;           /* count this game */
10264                 lastLoadGameStart = cm;
10265                 break;
10266               case XBoardGame:
10267                 lastLoadGameStart = cm; /* game counted already */
10268                 break;
10269               default:
10270                 /* impossible */
10271                 break;
10272             }
10273             if (gn > 0) {
10274                 do {
10275                     yyboardindex = forwardMostMove;
10276                     cm = (ChessMove) Myylex();
10277                 } while (cm == PGNTag || cm == Comment);
10278             }
10279             break;
10280
10281           case WhiteWins:
10282           case BlackWins:
10283           case GameIsDrawn:
10284             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10285                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10286                     != CMAIL_OLD_RESULT) {
10287                     nCmailResults ++ ;
10288                     cmailResult[  CMAIL_MAX_GAMES
10289                                 - gn - 1] = CMAIL_OLD_RESULT;
10290                 }
10291             }
10292             break;
10293
10294           case NormalMove:
10295             /* Only a NormalMove can be at the start of a game
10296              * without a position diagram. */
10297             if (lastLoadGameStart == EndOfFile ) {
10298               gn--;
10299               lastLoadGameStart = MoveNumberOne;
10300             }
10301             break;
10302
10303           default:
10304             break;
10305         }
10306     }
10307
10308     if (appData.debugMode)
10309       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10310
10311     if (cm == XBoardGame) {
10312         /* Skip any header junk before position diagram and/or move 1 */
10313         for (;;) {
10314             yyboardindex = forwardMostMove;
10315             cm = (ChessMove) Myylex();
10316
10317             if (cm == EndOfFile ||
10318                 cm == GNUChessGame || cm == XBoardGame) {
10319                 /* Empty game; pretend end-of-file and handle later */
10320                 cm = EndOfFile;
10321                 break;
10322             }
10323
10324             if (cm == MoveNumberOne || cm == PositionDiagram ||
10325                 cm == PGNTag || cm == Comment)
10326               break;
10327         }
10328     } else if (cm == GNUChessGame) {
10329         if (gameInfo.event != NULL) {
10330             free(gameInfo.event);
10331         }
10332         gameInfo.event = StrSave(yy_text);
10333     }
10334
10335     startedFromSetupPosition = FALSE;
10336     while (cm == PGNTag) {
10337         if (appData.debugMode)
10338           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10339         err = ParsePGNTag(yy_text, &gameInfo);
10340         if (!err) numPGNTags++;
10341
10342         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10343         if(gameInfo.variant != oldVariant) {
10344             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10345             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10346             InitPosition(TRUE);
10347             oldVariant = gameInfo.variant;
10348             if (appData.debugMode)
10349               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10350         }
10351
10352
10353         if (gameInfo.fen != NULL) {
10354           Board initial_position;
10355           startedFromSetupPosition = TRUE;
10356           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10357             Reset(TRUE, TRUE);
10358             DisplayError(_("Bad FEN position in file"), 0);
10359             return FALSE;
10360           }
10361           CopyBoard(boards[0], initial_position);
10362           if (blackPlaysFirst) {
10363             currentMove = forwardMostMove = backwardMostMove = 1;
10364             CopyBoard(boards[1], initial_position);
10365             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10366             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10367             timeRemaining[0][1] = whiteTimeRemaining;
10368             timeRemaining[1][1] = blackTimeRemaining;
10369             if (commentList[0] != NULL) {
10370               commentList[1] = commentList[0];
10371               commentList[0] = NULL;
10372             }
10373           } else {
10374             currentMove = forwardMostMove = backwardMostMove = 0;
10375           }
10376           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10377           {   int i;
10378               initialRulePlies = FENrulePlies;
10379               for( i=0; i< nrCastlingRights; i++ )
10380                   initialRights[i] = initial_position[CASTLING][i];
10381           }
10382           yyboardindex = forwardMostMove;
10383           free(gameInfo.fen);
10384           gameInfo.fen = NULL;
10385         }
10386
10387         yyboardindex = forwardMostMove;
10388         cm = (ChessMove) Myylex();
10389
10390         /* Handle comments interspersed among the tags */
10391         while (cm == Comment) {
10392             char *p;
10393             if (appData.debugMode)
10394               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10395             p = yy_text;
10396             AppendComment(currentMove, p, FALSE);
10397             yyboardindex = forwardMostMove;
10398             cm = (ChessMove) Myylex();
10399         }
10400     }
10401
10402     /* don't rely on existence of Event tag since if game was
10403      * pasted from clipboard the Event tag may not exist
10404      */
10405     if (numPGNTags > 0){
10406         char *tags;
10407         if (gameInfo.variant == VariantNormal) {
10408           VariantClass v = StringToVariant(gameInfo.event);
10409           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10410           if(v < VariantShogi) gameInfo.variant = v;
10411         }
10412         if (!matchMode) {
10413           if( appData.autoDisplayTags ) {
10414             tags = PGNTags(&gameInfo);
10415             TagsPopUp(tags, CmailMsg());
10416             free(tags);
10417           }
10418         }
10419     } else {
10420         /* Make something up, but don't display it now */
10421         SetGameInfo();
10422         TagsPopDown();
10423     }
10424
10425     if (cm == PositionDiagram) {
10426         int i, j;
10427         char *p;
10428         Board initial_position;
10429
10430         if (appData.debugMode)
10431           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10432
10433         if (!startedFromSetupPosition) {
10434             p = yy_text;
10435             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10436               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10437                 switch (*p) {
10438                   case '[':
10439                   case '-':
10440                   case ' ':
10441                   case '\t':
10442                   case '\n':
10443                   case '\r':
10444                     break;
10445                   default:
10446                     initial_position[i][j++] = CharToPiece(*p);
10447                     break;
10448                 }
10449             while (*p == ' ' || *p == '\t' ||
10450                    *p == '\n' || *p == '\r') p++;
10451
10452             if (strncmp(p, "black", strlen("black"))==0)
10453               blackPlaysFirst = TRUE;
10454             else
10455               blackPlaysFirst = FALSE;
10456             startedFromSetupPosition = TRUE;
10457
10458             CopyBoard(boards[0], initial_position);
10459             if (blackPlaysFirst) {
10460                 currentMove = forwardMostMove = backwardMostMove = 1;
10461                 CopyBoard(boards[1], initial_position);
10462                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10463                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10464                 timeRemaining[0][1] = whiteTimeRemaining;
10465                 timeRemaining[1][1] = blackTimeRemaining;
10466                 if (commentList[0] != NULL) {
10467                     commentList[1] = commentList[0];
10468                     commentList[0] = NULL;
10469                 }
10470             } else {
10471                 currentMove = forwardMostMove = backwardMostMove = 0;
10472             }
10473         }
10474         yyboardindex = forwardMostMove;
10475         cm = (ChessMove) Myylex();
10476     }
10477
10478     if (first.pr == NoProc) {
10479         StartChessProgram(&first);
10480     }
10481     InitChessProgram(&first, FALSE);
10482     SendToProgram("force\n", &first);
10483     if (startedFromSetupPosition) {
10484         SendBoard(&first, forwardMostMove);
10485     if (appData.debugMode) {
10486         fprintf(debugFP, "Load Game\n");
10487     }
10488         DisplayBothClocks();
10489     }
10490
10491     /* [HGM] server: flag to write setup moves in broadcast file as one */
10492     loadFlag = appData.suppressLoadMoves;
10493
10494     while (cm == Comment) {
10495         char *p;
10496         if (appData.debugMode)
10497           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10498         p = yy_text;
10499         AppendComment(currentMove, p, FALSE);
10500         yyboardindex = forwardMostMove;
10501         cm = (ChessMove) Myylex();
10502     }
10503
10504     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10505         cm == WhiteWins || cm == BlackWins ||
10506         cm == GameIsDrawn || cm == GameUnfinished) {
10507         DisplayMessage("", _("No moves in game"));
10508         if (cmailMsgLoaded) {
10509             if (appData.debugMode)
10510               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10511             ClearHighlights();
10512             flipView = FALSE;
10513         }
10514         DrawPosition(FALSE, boards[currentMove]);
10515         DisplayBothClocks();
10516         gameMode = EditGame;
10517         ModeHighlight();
10518         gameFileFP = NULL;
10519         cmailOldMove = 0;
10520         return TRUE;
10521     }
10522
10523     // [HGM] PV info: routine tests if comment empty
10524     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10525         DisplayComment(currentMove - 1, commentList[currentMove]);
10526     }
10527     if (!matchMode && appData.timeDelay != 0)
10528       DrawPosition(FALSE, boards[currentMove]);
10529
10530     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10531       programStats.ok_to_send = 1;
10532     }
10533
10534     /* if the first token after the PGN tags is a move
10535      * and not move number 1, retrieve it from the parser
10536      */
10537     if (cm != MoveNumberOne)
10538         LoadGameOneMove(cm);
10539
10540     /* load the remaining moves from the file */
10541     while (LoadGameOneMove(EndOfFile)) {
10542       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10543       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10544     }
10545
10546     /* rewind to the start of the game */
10547     currentMove = backwardMostMove;
10548
10549     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10550
10551     if (oldGameMode == AnalyzeFile ||
10552         oldGameMode == AnalyzeMode) {
10553       AnalyzeFileEvent();
10554     }
10555
10556     if (matchMode || appData.timeDelay == 0) {
10557       ToEndEvent();
10558       gameMode = EditGame;
10559       ModeHighlight();
10560     } else if (appData.timeDelay > 0) {
10561       AutoPlayGameLoop();
10562     }
10563
10564     if (appData.debugMode)
10565         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10566
10567     loadFlag = 0; /* [HGM] true game starts */
10568     return TRUE;
10569 }
10570
10571 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10572 int
10573 ReloadPosition(offset)
10574      int offset;
10575 {
10576     int positionNumber = lastLoadPositionNumber + offset;
10577     if (lastLoadPositionFP == NULL) {
10578         DisplayError(_("No position has been loaded yet"), 0);
10579         return FALSE;
10580     }
10581     if (positionNumber <= 0) {
10582         DisplayError(_("Can't back up any further"), 0);
10583         return FALSE;
10584     }
10585     return LoadPosition(lastLoadPositionFP, positionNumber,
10586                         lastLoadPositionTitle);
10587 }
10588
10589 /* Load the nth position from the given file */
10590 int
10591 LoadPositionFromFile(filename, n, title)
10592      char *filename;
10593      int n;
10594      char *title;
10595 {
10596     FILE *f;
10597     char buf[MSG_SIZ];
10598
10599     if (strcmp(filename, "-") == 0) {
10600         return LoadPosition(stdin, n, "stdin");
10601     } else {
10602         f = fopen(filename, "rb");
10603         if (f == NULL) {
10604             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10605             DisplayError(buf, errno);
10606             return FALSE;
10607         } else {
10608             return LoadPosition(f, n, title);
10609         }
10610     }
10611 }
10612
10613 /* Load the nth position from the given open file, and close it */
10614 int
10615 LoadPosition(f, positionNumber, title)
10616      FILE *f;
10617      int positionNumber;
10618      char *title;
10619 {
10620     char *p, line[MSG_SIZ];
10621     Board initial_position;
10622     int i, j, fenMode, pn;
10623
10624     if (gameMode == Training )
10625         SetTrainingModeOff();
10626
10627     if (gameMode != BeginningOfGame) {
10628         Reset(FALSE, TRUE);
10629     }
10630     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10631         fclose(lastLoadPositionFP);
10632     }
10633     if (positionNumber == 0) positionNumber = 1;
10634     lastLoadPositionFP = f;
10635     lastLoadPositionNumber = positionNumber;
10636     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10637     if (first.pr == NoProc) {
10638       StartChessProgram(&first);
10639       InitChessProgram(&first, FALSE);
10640     }
10641     pn = positionNumber;
10642     if (positionNumber < 0) {
10643         /* Negative position number means to seek to that byte offset */
10644         if (fseek(f, -positionNumber, 0) == -1) {
10645             DisplayError(_("Can't seek on position file"), 0);
10646             return FALSE;
10647         };
10648         pn = 1;
10649     } else {
10650         if (fseek(f, 0, 0) == -1) {
10651             if (f == lastLoadPositionFP ?
10652                 positionNumber == lastLoadPositionNumber + 1 :
10653                 positionNumber == 1) {
10654                 pn = 1;
10655             } else {
10656                 DisplayError(_("Can't seek on position file"), 0);
10657                 return FALSE;
10658             }
10659         }
10660     }
10661     /* See if this file is FEN or old-style xboard */
10662     if (fgets(line, MSG_SIZ, f) == NULL) {
10663         DisplayError(_("Position not found in file"), 0);
10664         return FALSE;
10665     }
10666     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10667     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10668
10669     if (pn >= 2) {
10670         if (fenMode || line[0] == '#') pn--;
10671         while (pn > 0) {
10672             /* skip positions before number pn */
10673             if (fgets(line, MSG_SIZ, f) == NULL) {
10674                 Reset(TRUE, TRUE);
10675                 DisplayError(_("Position not found in file"), 0);
10676                 return FALSE;
10677             }
10678             if (fenMode || line[0] == '#') pn--;
10679         }
10680     }
10681
10682     if (fenMode) {
10683         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10684             DisplayError(_("Bad FEN position in file"), 0);
10685             return FALSE;
10686         }
10687     } else {
10688         (void) fgets(line, MSG_SIZ, f);
10689         (void) fgets(line, MSG_SIZ, f);
10690
10691         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10692             (void) fgets(line, MSG_SIZ, f);
10693             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10694                 if (*p == ' ')
10695                   continue;
10696                 initial_position[i][j++] = CharToPiece(*p);
10697             }
10698         }
10699
10700         blackPlaysFirst = FALSE;
10701         if (!feof(f)) {
10702             (void) fgets(line, MSG_SIZ, f);
10703             if (strncmp(line, "black", strlen("black"))==0)
10704               blackPlaysFirst = TRUE;
10705         }
10706     }
10707     startedFromSetupPosition = TRUE;
10708
10709     SendToProgram("force\n", &first);
10710     CopyBoard(boards[0], initial_position);
10711     if (blackPlaysFirst) {
10712         currentMove = forwardMostMove = backwardMostMove = 1;
10713         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10714         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10715         CopyBoard(boards[1], initial_position);
10716         DisplayMessage("", _("Black to play"));
10717     } else {
10718         currentMove = forwardMostMove = backwardMostMove = 0;
10719         DisplayMessage("", _("White to play"));
10720     }
10721     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10722     SendBoard(&first, forwardMostMove);
10723     if (appData.debugMode) {
10724 int i, j;
10725   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10726   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10727         fprintf(debugFP, "Load Position\n");
10728     }
10729
10730     if (positionNumber > 1) {
10731       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10732         DisplayTitle(line);
10733     } else {
10734         DisplayTitle(title);
10735     }
10736     gameMode = EditGame;
10737     ModeHighlight();
10738     ResetClocks();
10739     timeRemaining[0][1] = whiteTimeRemaining;
10740     timeRemaining[1][1] = blackTimeRemaining;
10741     DrawPosition(FALSE, boards[currentMove]);
10742
10743     return TRUE;
10744 }
10745
10746
10747 void
10748 CopyPlayerNameIntoFileName(dest, src)
10749      char **dest, *src;
10750 {
10751     while (*src != NULLCHAR && *src != ',') {
10752         if (*src == ' ') {
10753             *(*dest)++ = '_';
10754             src++;
10755         } else {
10756             *(*dest)++ = *src++;
10757         }
10758     }
10759 }
10760
10761 char *DefaultFileName(ext)
10762      char *ext;
10763 {
10764     static char def[MSG_SIZ];
10765     char *p;
10766
10767     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10768         p = def;
10769         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10770         *p++ = '-';
10771         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10772         *p++ = '.';
10773         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10774     } else {
10775         def[0] = NULLCHAR;
10776     }
10777     return def;
10778 }
10779
10780 /* Save the current game to the given file */
10781 int
10782 SaveGameToFile(filename, append)
10783      char *filename;
10784      int append;
10785 {
10786     FILE *f;
10787     char buf[MSG_SIZ];
10788
10789     if (strcmp(filename, "-") == 0) {
10790         return SaveGame(stdout, 0, NULL);
10791     } else {
10792         f = fopen(filename, append ? "a" : "w");
10793         if (f == NULL) {
10794             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10795             DisplayError(buf, errno);
10796             return FALSE;
10797         } else {
10798             return SaveGame(f, 0, NULL);
10799         }
10800     }
10801 }
10802
10803 char *
10804 SavePart(str)
10805      char *str;
10806 {
10807     static char buf[MSG_SIZ];
10808     char *p;
10809
10810     p = strchr(str, ' ');
10811     if (p == NULL) return str;
10812     strncpy(buf, str, p - str);
10813     buf[p - str] = NULLCHAR;
10814     return buf;
10815 }
10816
10817 #define PGN_MAX_LINE 75
10818
10819 #define PGN_SIDE_WHITE  0
10820 #define PGN_SIDE_BLACK  1
10821
10822 /* [AS] */
10823 static int FindFirstMoveOutOfBook( int side )
10824 {
10825     int result = -1;
10826
10827     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10828         int index = backwardMostMove;
10829         int has_book_hit = 0;
10830
10831         if( (index % 2) != side ) {
10832             index++;
10833         }
10834
10835         while( index < forwardMostMove ) {
10836             /* Check to see if engine is in book */
10837             int depth = pvInfoList[index].depth;
10838             int score = pvInfoList[index].score;
10839             int in_book = 0;
10840
10841             if( depth <= 2 ) {
10842                 in_book = 1;
10843             }
10844             else if( score == 0 && depth == 63 ) {
10845                 in_book = 1; /* Zappa */
10846             }
10847             else if( score == 2 && depth == 99 ) {
10848                 in_book = 1; /* Abrok */
10849             }
10850
10851             has_book_hit += in_book;
10852
10853             if( ! in_book ) {
10854                 result = index;
10855
10856                 break;
10857             }
10858
10859             index += 2;
10860         }
10861     }
10862
10863     return result;
10864 }
10865
10866 /* [AS] */
10867 void GetOutOfBookInfo( char * buf )
10868 {
10869     int oob[2];
10870     int i;
10871     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10872
10873     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10874     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10875
10876     *buf = '\0';
10877
10878     if( oob[0] >= 0 || oob[1] >= 0 ) {
10879         for( i=0; i<2; i++ ) {
10880             int idx = oob[i];
10881
10882             if( idx >= 0 ) {
10883                 if( i > 0 && oob[0] >= 0 ) {
10884                     strcat( buf, "   " );
10885                 }
10886
10887                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10888                 sprintf( buf+strlen(buf), "%s%.2f",
10889                     pvInfoList[idx].score >= 0 ? "+" : "",
10890                     pvInfoList[idx].score / 100.0 );
10891             }
10892         }
10893     }
10894 }
10895
10896 /* Save game in PGN style and close the file */
10897 int
10898 SaveGamePGN(f)
10899      FILE *f;
10900 {
10901     int i, offset, linelen, newblock;
10902     time_t tm;
10903 //    char *movetext;
10904     char numtext[32];
10905     int movelen, numlen, blank;
10906     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10907
10908     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10909
10910     tm = time((time_t *) NULL);
10911
10912     PrintPGNTags(f, &gameInfo);
10913
10914     if (backwardMostMove > 0 || startedFromSetupPosition) {
10915         char *fen = PositionToFEN(backwardMostMove, NULL);
10916         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10917         fprintf(f, "\n{--------------\n");
10918         PrintPosition(f, backwardMostMove);
10919         fprintf(f, "--------------}\n");
10920         free(fen);
10921     }
10922     else {
10923         /* [AS] Out of book annotation */
10924         if( appData.saveOutOfBookInfo ) {
10925             char buf[64];
10926
10927             GetOutOfBookInfo( buf );
10928
10929             if( buf[0] != '\0' ) {
10930                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10931             }
10932         }
10933
10934         fprintf(f, "\n");
10935     }
10936
10937     i = backwardMostMove;
10938     linelen = 0;
10939     newblock = TRUE;
10940
10941     while (i < forwardMostMove) {
10942         /* Print comments preceding this move */
10943         if (commentList[i] != NULL) {
10944             if (linelen > 0) fprintf(f, "\n");
10945             fprintf(f, "%s", commentList[i]);
10946             linelen = 0;
10947             newblock = TRUE;
10948         }
10949
10950         /* Format move number */
10951         if ((i % 2) == 0)
10952           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10953         else
10954           if (newblock)
10955             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10956           else
10957             numtext[0] = NULLCHAR;
10958
10959         numlen = strlen(numtext);
10960         newblock = FALSE;
10961
10962         /* Print move number */
10963         blank = linelen > 0 && numlen > 0;
10964         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10965             fprintf(f, "\n");
10966             linelen = 0;
10967             blank = 0;
10968         }
10969         if (blank) {
10970             fprintf(f, " ");
10971             linelen++;
10972         }
10973         fprintf(f, "%s", numtext);
10974         linelen += numlen;
10975
10976         /* Get move */
10977         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10978         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10979
10980         /* Print move */
10981         blank = linelen > 0 && movelen > 0;
10982         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10983             fprintf(f, "\n");
10984             linelen = 0;
10985             blank = 0;
10986         }
10987         if (blank) {
10988             fprintf(f, " ");
10989             linelen++;
10990         }
10991         fprintf(f, "%s", move_buffer);
10992         linelen += movelen;
10993
10994         /* [AS] Add PV info if present */
10995         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10996             /* [HGM] add time */
10997             char buf[MSG_SIZ]; int seconds;
10998
10999             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11000
11001             if( seconds <= 0)
11002               buf[0] = 0;
11003             else
11004               if( seconds < 30 )
11005                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11006               else
11007                 {
11008                   seconds = (seconds + 4)/10; // round to full seconds
11009                   if( seconds < 60 )
11010                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11011                   else
11012                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11013                 }
11014
11015             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11016                       pvInfoList[i].score >= 0 ? "+" : "",
11017                       pvInfoList[i].score / 100.0,
11018                       pvInfoList[i].depth,
11019                       buf );
11020
11021             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11022
11023             /* Print score/depth */
11024             blank = linelen > 0 && movelen > 0;
11025             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11026                 fprintf(f, "\n");
11027                 linelen = 0;
11028                 blank = 0;
11029             }
11030             if (blank) {
11031                 fprintf(f, " ");
11032                 linelen++;
11033             }
11034             fprintf(f, "%s", move_buffer);
11035             linelen += movelen;
11036         }
11037
11038         i++;
11039     }
11040
11041     /* Start a new line */
11042     if (linelen > 0) fprintf(f, "\n");
11043
11044     /* Print comments after last move */
11045     if (commentList[i] != NULL) {
11046         fprintf(f, "%s\n", commentList[i]);
11047     }
11048
11049     /* Print result */
11050     if (gameInfo.resultDetails != NULL &&
11051         gameInfo.resultDetails[0] != NULLCHAR) {
11052         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11053                 PGNResult(gameInfo.result));
11054     } else {
11055         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11056     }
11057
11058     fclose(f);
11059     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11060     return TRUE;
11061 }
11062
11063 /* Save game in old style and close the file */
11064 int
11065 SaveGameOldStyle(f)
11066      FILE *f;
11067 {
11068     int i, offset;
11069     time_t tm;
11070
11071     tm = time((time_t *) NULL);
11072
11073     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11074     PrintOpponents(f);
11075
11076     if (backwardMostMove > 0 || startedFromSetupPosition) {
11077         fprintf(f, "\n[--------------\n");
11078         PrintPosition(f, backwardMostMove);
11079         fprintf(f, "--------------]\n");
11080     } else {
11081         fprintf(f, "\n");
11082     }
11083
11084     i = backwardMostMove;
11085     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11086
11087     while (i < forwardMostMove) {
11088         if (commentList[i] != NULL) {
11089             fprintf(f, "[%s]\n", commentList[i]);
11090         }
11091
11092         if ((i % 2) == 1) {
11093             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11094             i++;
11095         } else {
11096             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11097             i++;
11098             if (commentList[i] != NULL) {
11099                 fprintf(f, "\n");
11100                 continue;
11101             }
11102             if (i >= forwardMostMove) {
11103                 fprintf(f, "\n");
11104                 break;
11105             }
11106             fprintf(f, "%s\n", parseList[i]);
11107             i++;
11108         }
11109     }
11110
11111     if (commentList[i] != NULL) {
11112         fprintf(f, "[%s]\n", commentList[i]);
11113     }
11114
11115     /* This isn't really the old style, but it's close enough */
11116     if (gameInfo.resultDetails != NULL &&
11117         gameInfo.resultDetails[0] != NULLCHAR) {
11118         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11119                 gameInfo.resultDetails);
11120     } else {
11121         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11122     }
11123
11124     fclose(f);
11125     return TRUE;
11126 }
11127
11128 /* Save the current game to open file f and close the file */
11129 int
11130 SaveGame(f, dummy, dummy2)
11131      FILE *f;
11132      int dummy;
11133      char *dummy2;
11134 {
11135     if (gameMode == EditPosition) EditPositionDone(TRUE);
11136     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11137     if (appData.oldSaveStyle)
11138       return SaveGameOldStyle(f);
11139     else
11140       return SaveGamePGN(f);
11141 }
11142
11143 /* Save the current position to the given file */
11144 int
11145 SavePositionToFile(filename)
11146      char *filename;
11147 {
11148     FILE *f;
11149     char buf[MSG_SIZ];
11150
11151     if (strcmp(filename, "-") == 0) {
11152         return SavePosition(stdout, 0, NULL);
11153     } else {
11154         f = fopen(filename, "a");
11155         if (f == NULL) {
11156             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11157             DisplayError(buf, errno);
11158             return FALSE;
11159         } else {
11160             SavePosition(f, 0, NULL);
11161             return TRUE;
11162         }
11163     }
11164 }
11165
11166 /* Save the current position to the given open file and close the file */
11167 int
11168 SavePosition(f, dummy, dummy2)
11169      FILE *f;
11170      int dummy;
11171      char *dummy2;
11172 {
11173     time_t tm;
11174     char *fen;
11175
11176     if (gameMode == EditPosition) EditPositionDone(TRUE);
11177     if (appData.oldSaveStyle) {
11178         tm = time((time_t *) NULL);
11179
11180         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11181         PrintOpponents(f);
11182         fprintf(f, "[--------------\n");
11183         PrintPosition(f, currentMove);
11184         fprintf(f, "--------------]\n");
11185     } else {
11186         fen = PositionToFEN(currentMove, NULL);
11187         fprintf(f, "%s\n", fen);
11188         free(fen);
11189     }
11190     fclose(f);
11191     return TRUE;
11192 }
11193
11194 void
11195 ReloadCmailMsgEvent(unregister)
11196      int unregister;
11197 {
11198 #if !WIN32
11199     static char *inFilename = NULL;
11200     static char *outFilename;
11201     int i;
11202     struct stat inbuf, outbuf;
11203     int status;
11204
11205     /* Any registered moves are unregistered if unregister is set, */
11206     /* i.e. invoked by the signal handler */
11207     if (unregister) {
11208         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11209             cmailMoveRegistered[i] = FALSE;
11210             if (cmailCommentList[i] != NULL) {
11211                 free(cmailCommentList[i]);
11212                 cmailCommentList[i] = NULL;
11213             }
11214         }
11215         nCmailMovesRegistered = 0;
11216     }
11217
11218     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11219         cmailResult[i] = CMAIL_NOT_RESULT;
11220     }
11221     nCmailResults = 0;
11222
11223     if (inFilename == NULL) {
11224         /* Because the filenames are static they only get malloced once  */
11225         /* and they never get freed                                      */
11226         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11227         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11228
11229         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11230         sprintf(outFilename, "%s.out", appData.cmailGameName);
11231     }
11232
11233     status = stat(outFilename, &outbuf);
11234     if (status < 0) {
11235         cmailMailedMove = FALSE;
11236     } else {
11237         status = stat(inFilename, &inbuf);
11238         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11239     }
11240
11241     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11242        counts the games, notes how each one terminated, etc.
11243
11244        It would be nice to remove this kludge and instead gather all
11245        the information while building the game list.  (And to keep it
11246        in the game list nodes instead of having a bunch of fixed-size
11247        parallel arrays.)  Note this will require getting each game's
11248        termination from the PGN tags, as the game list builder does
11249        not process the game moves.  --mann
11250        */
11251     cmailMsgLoaded = TRUE;
11252     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11253
11254     /* Load first game in the file or popup game menu */
11255     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11256
11257 #endif /* !WIN32 */
11258     return;
11259 }
11260
11261 int
11262 RegisterMove()
11263 {
11264     FILE *f;
11265     char string[MSG_SIZ];
11266
11267     if (   cmailMailedMove
11268         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11269         return TRUE;            /* Allow free viewing  */
11270     }
11271
11272     /* Unregister move to ensure that we don't leave RegisterMove        */
11273     /* with the move registered when the conditions for registering no   */
11274     /* longer hold                                                       */
11275     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11276         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11277         nCmailMovesRegistered --;
11278
11279         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11280           {
11281               free(cmailCommentList[lastLoadGameNumber - 1]);
11282               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11283           }
11284     }
11285
11286     if (cmailOldMove == -1) {
11287         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11288         return FALSE;
11289     }
11290
11291     if (currentMove > cmailOldMove + 1) {
11292         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11293         return FALSE;
11294     }
11295
11296     if (currentMove < cmailOldMove) {
11297         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11298         return FALSE;
11299     }
11300
11301     if (forwardMostMove > currentMove) {
11302         /* Silently truncate extra moves */
11303         TruncateGame();
11304     }
11305
11306     if (   (currentMove == cmailOldMove + 1)
11307         || (   (currentMove == cmailOldMove)
11308             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11309                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11310         if (gameInfo.result != GameUnfinished) {
11311             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11312         }
11313
11314         if (commentList[currentMove] != NULL) {
11315             cmailCommentList[lastLoadGameNumber - 1]
11316               = StrSave(commentList[currentMove]);
11317         }
11318         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11319
11320         if (appData.debugMode)
11321           fprintf(debugFP, "Saving %s for game %d\n",
11322                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11323
11324         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11325
11326         f = fopen(string, "w");
11327         if (appData.oldSaveStyle) {
11328             SaveGameOldStyle(f); /* also closes the file */
11329
11330             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11331             f = fopen(string, "w");
11332             SavePosition(f, 0, NULL); /* also closes the file */
11333         } else {
11334             fprintf(f, "{--------------\n");
11335             PrintPosition(f, currentMove);
11336             fprintf(f, "--------------}\n\n");
11337
11338             SaveGame(f, 0, NULL); /* also closes the file*/
11339         }
11340
11341         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11342         nCmailMovesRegistered ++;
11343     } else if (nCmailGames == 1) {
11344         DisplayError(_("You have not made a move yet"), 0);
11345         return FALSE;
11346     }
11347
11348     return TRUE;
11349 }
11350
11351 void
11352 MailMoveEvent()
11353 {
11354 #if !WIN32
11355     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11356     FILE *commandOutput;
11357     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11358     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11359     int nBuffers;
11360     int i;
11361     int archived;
11362     char *arcDir;
11363
11364     if (! cmailMsgLoaded) {
11365         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11366         return;
11367     }
11368
11369     if (nCmailGames == nCmailResults) {
11370         DisplayError(_("No unfinished games"), 0);
11371         return;
11372     }
11373
11374 #if CMAIL_PROHIBIT_REMAIL
11375     if (cmailMailedMove) {
11376       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);
11377         DisplayError(msg, 0);
11378         return;
11379     }
11380 #endif
11381
11382     if (! (cmailMailedMove || RegisterMove())) return;
11383
11384     if (   cmailMailedMove
11385         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11386       snprintf(string, MSG_SIZ, partCommandString,
11387                appData.debugMode ? " -v" : "", appData.cmailGameName);
11388         commandOutput = popen(string, "r");
11389
11390         if (commandOutput == NULL) {
11391             DisplayError(_("Failed to invoke cmail"), 0);
11392         } else {
11393             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11394                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11395             }
11396             if (nBuffers > 1) {
11397                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11398                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11399                 nBytes = MSG_SIZ - 1;
11400             } else {
11401                 (void) memcpy(msg, buffer, nBytes);
11402             }
11403             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11404
11405             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11406                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11407
11408                 archived = TRUE;
11409                 for (i = 0; i < nCmailGames; i ++) {
11410                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11411                         archived = FALSE;
11412                     }
11413                 }
11414                 if (   archived
11415                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11416                         != NULL)) {
11417                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11418                            arcDir,
11419                            appData.cmailGameName,
11420                            gameInfo.date);
11421                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11422                     cmailMsgLoaded = FALSE;
11423                 }
11424             }
11425
11426             DisplayInformation(msg);
11427             pclose(commandOutput);
11428         }
11429     } else {
11430         if ((*cmailMsg) != '\0') {
11431             DisplayInformation(cmailMsg);
11432         }
11433     }
11434
11435     return;
11436 #endif /* !WIN32 */
11437 }
11438
11439 char *
11440 CmailMsg()
11441 {
11442 #if WIN32
11443     return NULL;
11444 #else
11445     int  prependComma = 0;
11446     char number[5];
11447     char string[MSG_SIZ];       /* Space for game-list */
11448     int  i;
11449
11450     if (!cmailMsgLoaded) return "";
11451
11452     if (cmailMailedMove) {
11453       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11454     } else {
11455         /* Create a list of games left */
11456       snprintf(string, MSG_SIZ, "[");
11457         for (i = 0; i < nCmailGames; i ++) {
11458             if (! (   cmailMoveRegistered[i]
11459                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11460                 if (prependComma) {
11461                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11462                 } else {
11463                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11464                     prependComma = 1;
11465                 }
11466
11467                 strcat(string, number);
11468             }
11469         }
11470         strcat(string, "]");
11471
11472         if (nCmailMovesRegistered + nCmailResults == 0) {
11473             switch (nCmailGames) {
11474               case 1:
11475                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11476                 break;
11477
11478               case 2:
11479                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11480                 break;
11481
11482               default:
11483                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11484                          nCmailGames);
11485                 break;
11486             }
11487         } else {
11488             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11489               case 1:
11490                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11491                          string);
11492                 break;
11493
11494               case 0:
11495                 if (nCmailResults == nCmailGames) {
11496                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11497                 } else {
11498                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11499                 }
11500                 break;
11501
11502               default:
11503                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11504                          string);
11505             }
11506         }
11507     }
11508     return cmailMsg;
11509 #endif /* WIN32 */
11510 }
11511
11512 void
11513 ResetGameEvent()
11514 {
11515     if (gameMode == Training)
11516       SetTrainingModeOff();
11517
11518     Reset(TRUE, TRUE);
11519     cmailMsgLoaded = FALSE;
11520     if (appData.icsActive) {
11521       SendToICS(ics_prefix);
11522       SendToICS("refresh\n");
11523     }
11524 }
11525
11526 void
11527 ExitEvent(status)
11528      int status;
11529 {
11530     exiting++;
11531     if (exiting > 2) {
11532       /* Give up on clean exit */
11533       exit(status);
11534     }
11535     if (exiting > 1) {
11536       /* Keep trying for clean exit */
11537       return;
11538     }
11539
11540     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11541
11542     if (telnetISR != NULL) {
11543       RemoveInputSource(telnetISR);
11544     }
11545     if (icsPR != NoProc) {
11546       DestroyChildProcess(icsPR, TRUE);
11547     }
11548
11549     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11550     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11551
11552     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11553     /* make sure this other one finishes before killing it!                  */
11554     if(endingGame) { int count = 0;
11555         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11556         while(endingGame && count++ < 10) DoSleep(1);
11557         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11558     }
11559
11560     /* Kill off chess programs */
11561     if (first.pr != NoProc) {
11562         ExitAnalyzeMode();
11563
11564         DoSleep( appData.delayBeforeQuit );
11565         SendToProgram("quit\n", &first);
11566         DoSleep( appData.delayAfterQuit );
11567         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11568     }
11569     if (second.pr != NoProc) {
11570         DoSleep( appData.delayBeforeQuit );
11571         SendToProgram("quit\n", &second);
11572         DoSleep( appData.delayAfterQuit );
11573         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11574     }
11575     if (first.isr != NULL) {
11576         RemoveInputSource(first.isr);
11577     }
11578     if (second.isr != NULL) {
11579         RemoveInputSource(second.isr);
11580     }
11581
11582     ShutDownFrontEnd();
11583     exit(status);
11584 }
11585
11586 void
11587 PauseEvent()
11588 {
11589     if (appData.debugMode)
11590         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11591     if (pausing) {
11592         pausing = FALSE;
11593         ModeHighlight();
11594         if (gameMode == MachinePlaysWhite ||
11595             gameMode == MachinePlaysBlack) {
11596             StartClocks();
11597         } else {
11598             DisplayBothClocks();
11599         }
11600         if (gameMode == PlayFromGameFile) {
11601             if (appData.timeDelay >= 0)
11602                 AutoPlayGameLoop();
11603         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11604             Reset(FALSE, TRUE);
11605             SendToICS(ics_prefix);
11606             SendToICS("refresh\n");
11607         } else if (currentMove < forwardMostMove) {
11608             ForwardInner(forwardMostMove);
11609         }
11610         pauseExamInvalid = FALSE;
11611     } else {
11612         switch (gameMode) {
11613           default:
11614             return;
11615           case IcsExamining:
11616             pauseExamForwardMostMove = forwardMostMove;
11617             pauseExamInvalid = FALSE;
11618             /* fall through */
11619           case IcsObserving:
11620           case IcsPlayingWhite:
11621           case IcsPlayingBlack:
11622             pausing = TRUE;
11623             ModeHighlight();
11624             return;
11625           case PlayFromGameFile:
11626             (void) StopLoadGameTimer();
11627             pausing = TRUE;
11628             ModeHighlight();
11629             break;
11630           case BeginningOfGame:
11631             if (appData.icsActive) return;
11632             /* else fall through */
11633           case MachinePlaysWhite:
11634           case MachinePlaysBlack:
11635           case TwoMachinesPlay:
11636             if (forwardMostMove == 0)
11637               return;           /* don't pause if no one has moved */
11638             if ((gameMode == MachinePlaysWhite &&
11639                  !WhiteOnMove(forwardMostMove)) ||
11640                 (gameMode == MachinePlaysBlack &&
11641                  WhiteOnMove(forwardMostMove))) {
11642                 StopClocks();
11643             }
11644             pausing = TRUE;
11645             ModeHighlight();
11646             break;
11647         }
11648     }
11649 }
11650
11651 void
11652 EditCommentEvent()
11653 {
11654     char title[MSG_SIZ];
11655
11656     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11657       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11658     } else {
11659       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11660                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11661                parseList[currentMove - 1]);
11662     }
11663
11664     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11665 }
11666
11667
11668 void
11669 EditTagsEvent()
11670 {
11671     char *tags = PGNTags(&gameInfo);
11672     EditTagsPopUp(tags, NULL);
11673     free(tags);
11674 }
11675
11676 void
11677 AnalyzeModeEvent()
11678 {
11679     if (appData.noChessProgram || gameMode == AnalyzeMode)
11680       return;
11681
11682     if (gameMode != AnalyzeFile) {
11683         if (!appData.icsEngineAnalyze) {
11684                EditGameEvent();
11685                if (gameMode != EditGame) return;
11686         }
11687         ResurrectChessProgram();
11688         SendToProgram("analyze\n", &first);
11689         first.analyzing = TRUE;
11690         /*first.maybeThinking = TRUE;*/
11691         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11692         EngineOutputPopUp();
11693     }
11694     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11695     pausing = FALSE;
11696     ModeHighlight();
11697     SetGameInfo();
11698
11699     StartAnalysisClock();
11700     GetTimeMark(&lastNodeCountTime);
11701     lastNodeCount = 0;
11702 }
11703
11704 void
11705 AnalyzeFileEvent()
11706 {
11707     if (appData.noChessProgram || gameMode == AnalyzeFile)
11708       return;
11709
11710     if (gameMode != AnalyzeMode) {
11711         EditGameEvent();
11712         if (gameMode != EditGame) return;
11713         ResurrectChessProgram();
11714         SendToProgram("analyze\n", &first);
11715         first.analyzing = TRUE;
11716         /*first.maybeThinking = TRUE;*/
11717         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11718         EngineOutputPopUp();
11719     }
11720     gameMode = AnalyzeFile;
11721     pausing = FALSE;
11722     ModeHighlight();
11723     SetGameInfo();
11724
11725     StartAnalysisClock();
11726     GetTimeMark(&lastNodeCountTime);
11727     lastNodeCount = 0;
11728 }
11729
11730 void
11731 MachineWhiteEvent()
11732 {
11733     char buf[MSG_SIZ];
11734     char *bookHit = NULL;
11735
11736     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11737       return;
11738
11739
11740     if (gameMode == PlayFromGameFile ||
11741         gameMode == TwoMachinesPlay  ||
11742         gameMode == Training         ||
11743         gameMode == AnalyzeMode      ||
11744         gameMode == EndOfGame)
11745         EditGameEvent();
11746
11747     if (gameMode == EditPosition)
11748         EditPositionDone(TRUE);
11749
11750     if (!WhiteOnMove(currentMove)) {
11751         DisplayError(_("It is not White's turn"), 0);
11752         return;
11753     }
11754
11755     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11756       ExitAnalyzeMode();
11757
11758     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11759         gameMode == AnalyzeFile)
11760         TruncateGame();
11761
11762     ResurrectChessProgram();    /* in case it isn't running */
11763     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11764         gameMode = MachinePlaysWhite;
11765         ResetClocks();
11766     } else
11767     gameMode = MachinePlaysWhite;
11768     pausing = FALSE;
11769     ModeHighlight();
11770     SetGameInfo();
11771     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11772     DisplayTitle(buf);
11773     if (first.sendName) {
11774       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11775       SendToProgram(buf, &first);
11776     }
11777     if (first.sendTime) {
11778       if (first.useColors) {
11779         SendToProgram("black\n", &first); /*gnu kludge*/
11780       }
11781       SendTimeRemaining(&first, TRUE);
11782     }
11783     if (first.useColors) {
11784       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11785     }
11786     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11787     SetMachineThinkingEnables();
11788     first.maybeThinking = TRUE;
11789     StartClocks();
11790     firstMove = FALSE;
11791
11792     if (appData.autoFlipView && !flipView) {
11793       flipView = !flipView;
11794       DrawPosition(FALSE, NULL);
11795       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11796     }
11797
11798     if(bookHit) { // [HGM] book: simulate book reply
11799         static char bookMove[MSG_SIZ]; // a bit generous?
11800
11801         programStats.nodes = programStats.depth = programStats.time =
11802         programStats.score = programStats.got_only_move = 0;
11803         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11804
11805         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11806         strcat(bookMove, bookHit);
11807         HandleMachineMove(bookMove, &first);
11808     }
11809 }
11810
11811 void
11812 MachineBlackEvent()
11813 {
11814   char buf[MSG_SIZ];
11815   char *bookHit = NULL;
11816
11817     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11818         return;
11819
11820
11821     if (gameMode == PlayFromGameFile ||
11822         gameMode == TwoMachinesPlay  ||
11823         gameMode == Training         ||
11824         gameMode == AnalyzeMode      ||
11825         gameMode == EndOfGame)
11826         EditGameEvent();
11827
11828     if (gameMode == EditPosition)
11829         EditPositionDone(TRUE);
11830
11831     if (WhiteOnMove(currentMove)) {
11832         DisplayError(_("It is not Black's turn"), 0);
11833         return;
11834     }
11835
11836     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11837       ExitAnalyzeMode();
11838
11839     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11840         gameMode == AnalyzeFile)
11841         TruncateGame();
11842
11843     ResurrectChessProgram();    /* in case it isn't running */
11844     gameMode = MachinePlaysBlack;
11845     pausing = FALSE;
11846     ModeHighlight();
11847     SetGameInfo();
11848     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11849     DisplayTitle(buf);
11850     if (first.sendName) {
11851       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11852       SendToProgram(buf, &first);
11853     }
11854     if (first.sendTime) {
11855       if (first.useColors) {
11856         SendToProgram("white\n", &first); /*gnu kludge*/
11857       }
11858       SendTimeRemaining(&first, FALSE);
11859     }
11860     if (first.useColors) {
11861       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11862     }
11863     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11864     SetMachineThinkingEnables();
11865     first.maybeThinking = TRUE;
11866     StartClocks();
11867
11868     if (appData.autoFlipView && flipView) {
11869       flipView = !flipView;
11870       DrawPosition(FALSE, NULL);
11871       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11872     }
11873     if(bookHit) { // [HGM] book: simulate book reply
11874         static char bookMove[MSG_SIZ]; // a bit generous?
11875
11876         programStats.nodes = programStats.depth = programStats.time =
11877         programStats.score = programStats.got_only_move = 0;
11878         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11879
11880         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11881         strcat(bookMove, bookHit);
11882         HandleMachineMove(bookMove, &first);
11883     }
11884 }
11885
11886
11887 void
11888 DisplayTwoMachinesTitle()
11889 {
11890     char buf[MSG_SIZ];
11891     if (appData.matchGames > 0) {
11892         if (first.twoMachinesColor[0] == 'w') {
11893           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11894                    gameInfo.white, gameInfo.black,
11895                    first.matchWins, second.matchWins,
11896                    matchGame - 1 - (first.matchWins + second.matchWins));
11897         } else {
11898           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11899                    gameInfo.white, gameInfo.black,
11900                    second.matchWins, first.matchWins,
11901                    matchGame - 1 - (first.matchWins + second.matchWins));
11902         }
11903     } else {
11904       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11905     }
11906     DisplayTitle(buf);
11907 }
11908
11909 void
11910 SettingsMenuIfReady()
11911 {
11912   if (second.lastPing != second.lastPong) {
11913     DisplayMessage("", _("Waiting for second chess program"));
11914     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11915     return;
11916   }
11917   ThawUI();
11918   DisplayMessage("", "");
11919   SettingsPopUp(&second);
11920 }
11921
11922 int
11923 WaitForSecond(DelayedEventCallback retry)
11924 {
11925     if (second.pr == NULL) {
11926         StartChessProgram(&second);
11927         if (second.protocolVersion == 1) {
11928           retry();
11929         } else {
11930           /* kludge: allow timeout for initial "feature" command */
11931           FreezeUI();
11932           DisplayMessage("", _("Starting second chess program"));
11933           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11934         }
11935         return 1;
11936     }
11937     return 0;
11938 }
11939
11940 void
11941 TwoMachinesEvent P((void))
11942 {
11943     int i;
11944     char buf[MSG_SIZ];
11945     ChessProgramState *onmove;
11946     char *bookHit = NULL;
11947
11948     if (appData.noChessProgram) return;
11949
11950     switch (gameMode) {
11951       case TwoMachinesPlay:
11952         return;
11953       case MachinePlaysWhite:
11954       case MachinePlaysBlack:
11955         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11956             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11957             return;
11958         }
11959         /* fall through */
11960       case BeginningOfGame:
11961       case PlayFromGameFile:
11962       case EndOfGame:
11963         EditGameEvent();
11964         if (gameMode != EditGame) return;
11965         break;
11966       case EditPosition:
11967         EditPositionDone(TRUE);
11968         break;
11969       case AnalyzeMode:
11970       case AnalyzeFile:
11971         ExitAnalyzeMode();
11972         break;
11973       case EditGame:
11974       default:
11975         break;
11976     }
11977
11978 //    forwardMostMove = currentMove;
11979     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11980     ResurrectChessProgram();    /* in case first program isn't running */
11981
11982     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11983     DisplayMessage("", "");
11984     InitChessProgram(&second, FALSE);
11985     SendToProgram("force\n", &second);
11986     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11987       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11988       return;
11989     }
11990     if (startedFromSetupPosition) {
11991         SendBoard(&second, backwardMostMove);
11992     if (appData.debugMode) {
11993         fprintf(debugFP, "Two Machines\n");
11994     }
11995     }
11996     for (i = backwardMostMove; i < forwardMostMove; i++) {
11997         SendMoveToProgram(i, &second);
11998     }
11999
12000     gameMode = TwoMachinesPlay;
12001     pausing = FALSE;
12002     ModeHighlight();
12003     SetGameInfo();
12004     DisplayTwoMachinesTitle();
12005     firstMove = TRUE;
12006     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12007         onmove = &first;
12008     } else {
12009         onmove = &second;
12010     }
12011
12012     SendToProgram(first.computerString, &first);
12013     if (first.sendName) {
12014       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12015       SendToProgram(buf, &first);
12016     }
12017     SendToProgram(second.computerString, &second);
12018     if (second.sendName) {
12019       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12020       SendToProgram(buf, &second);
12021     }
12022
12023     ResetClocks();
12024     if (!first.sendTime || !second.sendTime) {
12025         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12026         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12027     }
12028     if (onmove->sendTime) {
12029       if (onmove->useColors) {
12030         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12031       }
12032       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12033     }
12034     if (onmove->useColors) {
12035       SendToProgram(onmove->twoMachinesColor, onmove);
12036     }
12037     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12038 //    SendToProgram("go\n", onmove);
12039     onmove->maybeThinking = TRUE;
12040     SetMachineThinkingEnables();
12041
12042     StartClocks();
12043
12044     if(bookHit) { // [HGM] book: simulate book reply
12045         static char bookMove[MSG_SIZ]; // a bit generous?
12046
12047         programStats.nodes = programStats.depth = programStats.time =
12048         programStats.score = programStats.got_only_move = 0;
12049         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12050
12051         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12052         strcat(bookMove, bookHit);
12053         savedMessage = bookMove; // args for deferred call
12054         savedState = onmove;
12055         ScheduleDelayedEvent(DeferredBookMove, 1);
12056     }
12057 }
12058
12059 void
12060 TrainingEvent()
12061 {
12062     if (gameMode == Training) {
12063       SetTrainingModeOff();
12064       gameMode = PlayFromGameFile;
12065       DisplayMessage("", _("Training mode off"));
12066     } else {
12067       gameMode = Training;
12068       animateTraining = appData.animate;
12069
12070       /* make sure we are not already at the end of the game */
12071       if (currentMove < forwardMostMove) {
12072         SetTrainingModeOn();
12073         DisplayMessage("", _("Training mode on"));
12074       } else {
12075         gameMode = PlayFromGameFile;
12076         DisplayError(_("Already at end of game"), 0);
12077       }
12078     }
12079     ModeHighlight();
12080 }
12081
12082 void
12083 IcsClientEvent()
12084 {
12085     if (!appData.icsActive) return;
12086     switch (gameMode) {
12087       case IcsPlayingWhite:
12088       case IcsPlayingBlack:
12089       case IcsObserving:
12090       case IcsIdle:
12091       case BeginningOfGame:
12092       case IcsExamining:
12093         return;
12094
12095       case EditGame:
12096         break;
12097
12098       case EditPosition:
12099         EditPositionDone(TRUE);
12100         break;
12101
12102       case AnalyzeMode:
12103       case AnalyzeFile:
12104         ExitAnalyzeMode();
12105         break;
12106
12107       default:
12108         EditGameEvent();
12109         break;
12110     }
12111
12112     gameMode = IcsIdle;
12113     ModeHighlight();
12114     return;
12115 }
12116
12117
12118 void
12119 EditGameEvent()
12120 {
12121     int i;
12122
12123     switch (gameMode) {
12124       case Training:
12125         SetTrainingModeOff();
12126         break;
12127       case MachinePlaysWhite:
12128       case MachinePlaysBlack:
12129       case BeginningOfGame:
12130         SendToProgram("force\n", &first);
12131         SetUserThinkingEnables();
12132         break;
12133       case PlayFromGameFile:
12134         (void) StopLoadGameTimer();
12135         if (gameFileFP != NULL) {
12136             gameFileFP = NULL;
12137         }
12138         break;
12139       case EditPosition:
12140         EditPositionDone(TRUE);
12141         break;
12142       case AnalyzeMode:
12143       case AnalyzeFile:
12144         ExitAnalyzeMode();
12145         SendToProgram("force\n", &first);
12146         break;
12147       case TwoMachinesPlay:
12148         GameEnds(EndOfFile, NULL, GE_PLAYER);
12149         ResurrectChessProgram();
12150         SetUserThinkingEnables();
12151         break;
12152       case EndOfGame:
12153         ResurrectChessProgram();
12154         break;
12155       case IcsPlayingBlack:
12156       case IcsPlayingWhite:
12157         DisplayError(_("Warning: You are still playing a game"), 0);
12158         break;
12159       case IcsObserving:
12160         DisplayError(_("Warning: You are still observing a game"), 0);
12161         break;
12162       case IcsExamining:
12163         DisplayError(_("Warning: You are still examining a game"), 0);
12164         break;
12165       case IcsIdle:
12166         break;
12167       case EditGame:
12168       default:
12169         return;
12170     }
12171
12172     pausing = FALSE;
12173     StopClocks();
12174     first.offeredDraw = second.offeredDraw = 0;
12175
12176     if (gameMode == PlayFromGameFile) {
12177         whiteTimeRemaining = timeRemaining[0][currentMove];
12178         blackTimeRemaining = timeRemaining[1][currentMove];
12179         DisplayTitle("");
12180     }
12181
12182     if (gameMode == MachinePlaysWhite ||
12183         gameMode == MachinePlaysBlack ||
12184         gameMode == TwoMachinesPlay ||
12185         gameMode == EndOfGame) {
12186         i = forwardMostMove;
12187         while (i > currentMove) {
12188             SendToProgram("undo\n", &first);
12189             i--;
12190         }
12191         whiteTimeRemaining = timeRemaining[0][currentMove];
12192         blackTimeRemaining = timeRemaining[1][currentMove];
12193         DisplayBothClocks();
12194         if (whiteFlag || blackFlag) {
12195             whiteFlag = blackFlag = 0;
12196         }
12197         DisplayTitle("");
12198     }
12199
12200     gameMode = EditGame;
12201     ModeHighlight();
12202     SetGameInfo();
12203 }
12204
12205
12206 void
12207 EditPositionEvent()
12208 {
12209     if (gameMode == EditPosition) {
12210         EditGameEvent();
12211         return;
12212     }
12213
12214     EditGameEvent();
12215     if (gameMode != EditGame) return;
12216
12217     gameMode = EditPosition;
12218     ModeHighlight();
12219     SetGameInfo();
12220     if (currentMove > 0)
12221       CopyBoard(boards[0], boards[currentMove]);
12222
12223     blackPlaysFirst = !WhiteOnMove(currentMove);
12224     ResetClocks();
12225     currentMove = forwardMostMove = backwardMostMove = 0;
12226     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12227     DisplayMove(-1);
12228 }
12229
12230 void
12231 ExitAnalyzeMode()
12232 {
12233     /* [DM] icsEngineAnalyze - possible call from other functions */
12234     if (appData.icsEngineAnalyze) {
12235         appData.icsEngineAnalyze = FALSE;
12236
12237         DisplayMessage("",_("Close ICS engine analyze..."));
12238     }
12239     if (first.analysisSupport && first.analyzing) {
12240       SendToProgram("exit\n", &first);
12241       first.analyzing = FALSE;
12242     }
12243     thinkOutput[0] = NULLCHAR;
12244 }
12245
12246 void
12247 EditPositionDone(Boolean fakeRights)
12248 {
12249     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12250
12251     startedFromSetupPosition = TRUE;
12252     InitChessProgram(&first, FALSE);
12253     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12254       boards[0][EP_STATUS] = EP_NONE;
12255       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12256     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12257         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12258         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12259       } else boards[0][CASTLING][2] = NoRights;
12260     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12261         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12262         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12263       } else boards[0][CASTLING][5] = NoRights;
12264     }
12265     SendToProgram("force\n", &first);
12266     if (blackPlaysFirst) {
12267         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12268         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12269         currentMove = forwardMostMove = backwardMostMove = 1;
12270         CopyBoard(boards[1], boards[0]);
12271     } else {
12272         currentMove = forwardMostMove = backwardMostMove = 0;
12273     }
12274     SendBoard(&first, forwardMostMove);
12275     if (appData.debugMode) {
12276         fprintf(debugFP, "EditPosDone\n");
12277     }
12278     DisplayTitle("");
12279     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12280     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12281     gameMode = EditGame;
12282     ModeHighlight();
12283     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12284     ClearHighlights(); /* [AS] */
12285 }
12286
12287 /* Pause for `ms' milliseconds */
12288 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12289 void
12290 TimeDelay(ms)
12291      long ms;
12292 {
12293     TimeMark m1, m2;
12294
12295     GetTimeMark(&m1);
12296     do {
12297         GetTimeMark(&m2);
12298     } while (SubtractTimeMarks(&m2, &m1) < ms);
12299 }
12300
12301 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12302 void
12303 SendMultiLineToICS(buf)
12304      char *buf;
12305 {
12306     char temp[MSG_SIZ+1], *p;
12307     int len;
12308
12309     len = strlen(buf);
12310     if (len > MSG_SIZ)
12311       len = MSG_SIZ;
12312
12313     strncpy(temp, buf, len);
12314     temp[len] = 0;
12315
12316     p = temp;
12317     while (*p) {
12318         if (*p == '\n' || *p == '\r')
12319           *p = ' ';
12320         ++p;
12321     }
12322
12323     strcat(temp, "\n");
12324     SendToICS(temp);
12325     SendToPlayer(temp, strlen(temp));
12326 }
12327
12328 void
12329 SetWhiteToPlayEvent()
12330 {
12331     if (gameMode == EditPosition) {
12332         blackPlaysFirst = FALSE;
12333         DisplayBothClocks();    /* works because currentMove is 0 */
12334     } else if (gameMode == IcsExamining) {
12335         SendToICS(ics_prefix);
12336         SendToICS("tomove white\n");
12337     }
12338 }
12339
12340 void
12341 SetBlackToPlayEvent()
12342 {
12343     if (gameMode == EditPosition) {
12344         blackPlaysFirst = TRUE;
12345         currentMove = 1;        /* kludge */
12346         DisplayBothClocks();
12347         currentMove = 0;
12348     } else if (gameMode == IcsExamining) {
12349         SendToICS(ics_prefix);
12350         SendToICS("tomove black\n");
12351     }
12352 }
12353
12354 void
12355 EditPositionMenuEvent(selection, x, y)
12356      ChessSquare selection;
12357      int x, y;
12358 {
12359     char buf[MSG_SIZ];
12360     ChessSquare piece = boards[0][y][x];
12361
12362     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12363
12364     switch (selection) {
12365       case ClearBoard:
12366         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12367             SendToICS(ics_prefix);
12368             SendToICS("bsetup clear\n");
12369         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12370             SendToICS(ics_prefix);
12371             SendToICS("clearboard\n");
12372         } else {
12373             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12374                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12375                 for (y = 0; y < BOARD_HEIGHT; y++) {
12376                     if (gameMode == IcsExamining) {
12377                         if (boards[currentMove][y][x] != EmptySquare) {
12378                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12379                                     AAA + x, ONE + y);
12380                             SendToICS(buf);
12381                         }
12382                     } else {
12383                         boards[0][y][x] = p;
12384                     }
12385                 }
12386             }
12387         }
12388         if (gameMode == EditPosition) {
12389             DrawPosition(FALSE, boards[0]);
12390         }
12391         break;
12392
12393       case WhitePlay:
12394         SetWhiteToPlayEvent();
12395         break;
12396
12397       case BlackPlay:
12398         SetBlackToPlayEvent();
12399         break;
12400
12401       case EmptySquare:
12402         if (gameMode == IcsExamining) {
12403             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12404             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12405             SendToICS(buf);
12406         } else {
12407             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12408                 if(x == BOARD_LEFT-2) {
12409                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12410                     boards[0][y][1] = 0;
12411                 } else
12412                 if(x == BOARD_RGHT+1) {
12413                     if(y >= gameInfo.holdingsSize) break;
12414                     boards[0][y][BOARD_WIDTH-2] = 0;
12415                 } else break;
12416             }
12417             boards[0][y][x] = EmptySquare;
12418             DrawPosition(FALSE, boards[0]);
12419         }
12420         break;
12421
12422       case PromotePiece:
12423         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12424            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12425             selection = (ChessSquare) (PROMOTED piece);
12426         } else if(piece == EmptySquare) selection = WhiteSilver;
12427         else selection = (ChessSquare)((int)piece - 1);
12428         goto defaultlabel;
12429
12430       case DemotePiece:
12431         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12432            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12433             selection = (ChessSquare) (DEMOTED piece);
12434         } else if(piece == EmptySquare) selection = BlackSilver;
12435         else selection = (ChessSquare)((int)piece + 1);
12436         goto defaultlabel;
12437
12438       case WhiteQueen:
12439       case BlackQueen:
12440         if(gameInfo.variant == VariantShatranj ||
12441            gameInfo.variant == VariantXiangqi  ||
12442            gameInfo.variant == VariantCourier  ||
12443            gameInfo.variant == VariantMakruk     )
12444             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12445         goto defaultlabel;
12446
12447       case WhiteKing:
12448       case BlackKing:
12449         if(gameInfo.variant == VariantXiangqi)
12450             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12451         if(gameInfo.variant == VariantKnightmate)
12452             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12453       default:
12454         defaultlabel:
12455         if (gameMode == IcsExamining) {
12456             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12457             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12458                      PieceToChar(selection), AAA + x, ONE + y);
12459             SendToICS(buf);
12460         } else {
12461             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12462                 int n;
12463                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12464                     n = PieceToNumber(selection - BlackPawn);
12465                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12466                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12467                     boards[0][BOARD_HEIGHT-1-n][1]++;
12468                 } else
12469                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12470                     n = PieceToNumber(selection);
12471                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12472                     boards[0][n][BOARD_WIDTH-1] = selection;
12473                     boards[0][n][BOARD_WIDTH-2]++;
12474                 }
12475             } else
12476             boards[0][y][x] = selection;
12477             DrawPosition(TRUE, boards[0]);
12478         }
12479         break;
12480     }
12481 }
12482
12483
12484 void
12485 DropMenuEvent(selection, x, y)
12486      ChessSquare selection;
12487      int x, y;
12488 {
12489     ChessMove moveType;
12490
12491     switch (gameMode) {
12492       case IcsPlayingWhite:
12493       case MachinePlaysBlack:
12494         if (!WhiteOnMove(currentMove)) {
12495             DisplayMoveError(_("It is Black's turn"));
12496             return;
12497         }
12498         moveType = WhiteDrop;
12499         break;
12500       case IcsPlayingBlack:
12501       case MachinePlaysWhite:
12502         if (WhiteOnMove(currentMove)) {
12503             DisplayMoveError(_("It is White's turn"));
12504             return;
12505         }
12506         moveType = BlackDrop;
12507         break;
12508       case EditGame:
12509         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12510         break;
12511       default:
12512         return;
12513     }
12514
12515     if (moveType == BlackDrop && selection < BlackPawn) {
12516       selection = (ChessSquare) ((int) selection
12517                                  + (int) BlackPawn - (int) WhitePawn);
12518     }
12519     if (boards[currentMove][y][x] != EmptySquare) {
12520         DisplayMoveError(_("That square is occupied"));
12521         return;
12522     }
12523
12524     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12525 }
12526
12527 void
12528 AcceptEvent()
12529 {
12530     /* Accept a pending offer of any kind from opponent */
12531
12532     if (appData.icsActive) {
12533         SendToICS(ics_prefix);
12534         SendToICS("accept\n");
12535     } else if (cmailMsgLoaded) {
12536         if (currentMove == cmailOldMove &&
12537             commentList[cmailOldMove] != NULL &&
12538             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12539                    "Black offers a draw" : "White offers a draw")) {
12540             TruncateGame();
12541             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12542             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12543         } else {
12544             DisplayError(_("There is no pending offer on this move"), 0);
12545             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12546         }
12547     } else {
12548         /* Not used for offers from chess program */
12549     }
12550 }
12551
12552 void
12553 DeclineEvent()
12554 {
12555     /* Decline a pending offer of any kind from opponent */
12556
12557     if (appData.icsActive) {
12558         SendToICS(ics_prefix);
12559         SendToICS("decline\n");
12560     } else if (cmailMsgLoaded) {
12561         if (currentMove == cmailOldMove &&
12562             commentList[cmailOldMove] != NULL &&
12563             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12564                    "Black offers a draw" : "White offers a draw")) {
12565 #ifdef NOTDEF
12566             AppendComment(cmailOldMove, "Draw declined", TRUE);
12567             DisplayComment(cmailOldMove - 1, "Draw declined");
12568 #endif /*NOTDEF*/
12569         } else {
12570             DisplayError(_("There is no pending offer on this move"), 0);
12571         }
12572     } else {
12573         /* Not used for offers from chess program */
12574     }
12575 }
12576
12577 void
12578 RematchEvent()
12579 {
12580     /* Issue ICS rematch command */
12581     if (appData.icsActive) {
12582         SendToICS(ics_prefix);
12583         SendToICS("rematch\n");
12584     }
12585 }
12586
12587 void
12588 CallFlagEvent()
12589 {
12590     /* Call your opponent's flag (claim a win on time) */
12591     if (appData.icsActive) {
12592         SendToICS(ics_prefix);
12593         SendToICS("flag\n");
12594     } else {
12595         switch (gameMode) {
12596           default:
12597             return;
12598           case MachinePlaysWhite:
12599             if (whiteFlag) {
12600                 if (blackFlag)
12601                   GameEnds(GameIsDrawn, "Both players ran out of time",
12602                            GE_PLAYER);
12603                 else
12604                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12605             } else {
12606                 DisplayError(_("Your opponent is not out of time"), 0);
12607             }
12608             break;
12609           case MachinePlaysBlack:
12610             if (blackFlag) {
12611                 if (whiteFlag)
12612                   GameEnds(GameIsDrawn, "Both players ran out of time",
12613                            GE_PLAYER);
12614                 else
12615                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12616             } else {
12617                 DisplayError(_("Your opponent is not out of time"), 0);
12618             }
12619             break;
12620         }
12621     }
12622 }
12623
12624 void
12625 DrawEvent()
12626 {
12627     /* Offer draw or accept pending draw offer from opponent */
12628
12629     if (appData.icsActive) {
12630         /* Note: tournament rules require draw offers to be
12631            made after you make your move but before you punch
12632            your clock.  Currently ICS doesn't let you do that;
12633            instead, you immediately punch your clock after making
12634            a move, but you can offer a draw at any time. */
12635
12636         SendToICS(ics_prefix);
12637         SendToICS("draw\n");
12638         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12639     } else if (cmailMsgLoaded) {
12640         if (currentMove == cmailOldMove &&
12641             commentList[cmailOldMove] != NULL &&
12642             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12643                    "Black offers a draw" : "White offers a draw")) {
12644             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12645             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12646         } else if (currentMove == cmailOldMove + 1) {
12647             char *offer = WhiteOnMove(cmailOldMove) ?
12648               "White offers a draw" : "Black offers a draw";
12649             AppendComment(currentMove, offer, TRUE);
12650             DisplayComment(currentMove - 1, offer);
12651             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12652         } else {
12653             DisplayError(_("You must make your move before offering a draw"), 0);
12654             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12655         }
12656     } else if (first.offeredDraw) {
12657         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12658     } else {
12659         if (first.sendDrawOffers) {
12660             SendToProgram("draw\n", &first);
12661             userOfferedDraw = TRUE;
12662         }
12663     }
12664 }
12665
12666 void
12667 AdjournEvent()
12668 {
12669     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12670
12671     if (appData.icsActive) {
12672         SendToICS(ics_prefix);
12673         SendToICS("adjourn\n");
12674     } else {
12675         /* Currently GNU Chess doesn't offer or accept Adjourns */
12676     }
12677 }
12678
12679
12680 void
12681 AbortEvent()
12682 {
12683     /* Offer Abort or accept pending Abort offer from opponent */
12684
12685     if (appData.icsActive) {
12686         SendToICS(ics_prefix);
12687         SendToICS("abort\n");
12688     } else {
12689         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12690     }
12691 }
12692
12693 void
12694 ResignEvent()
12695 {
12696     /* Resign.  You can do this even if it's not your turn. */
12697
12698     if (appData.icsActive) {
12699         SendToICS(ics_prefix);
12700         SendToICS("resign\n");
12701     } else {
12702         switch (gameMode) {
12703           case MachinePlaysWhite:
12704             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12705             break;
12706           case MachinePlaysBlack:
12707             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12708             break;
12709           case EditGame:
12710             if (cmailMsgLoaded) {
12711                 TruncateGame();
12712                 if (WhiteOnMove(cmailOldMove)) {
12713                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12714                 } else {
12715                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12716                 }
12717                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12718             }
12719             break;
12720           default:
12721             break;
12722         }
12723     }
12724 }
12725
12726
12727 void
12728 StopObservingEvent()
12729 {
12730     /* Stop observing current games */
12731     SendToICS(ics_prefix);
12732     SendToICS("unobserve\n");
12733 }
12734
12735 void
12736 StopExaminingEvent()
12737 {
12738     /* Stop observing current game */
12739     SendToICS(ics_prefix);
12740     SendToICS("unexamine\n");
12741 }
12742
12743 void
12744 ForwardInner(target)
12745      int target;
12746 {
12747     int limit;
12748
12749     if (appData.debugMode)
12750         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12751                 target, currentMove, forwardMostMove);
12752
12753     if (gameMode == EditPosition)
12754       return;
12755
12756     if (gameMode == PlayFromGameFile && !pausing)
12757       PauseEvent();
12758
12759     if (gameMode == IcsExamining && pausing)
12760       limit = pauseExamForwardMostMove;
12761     else
12762       limit = forwardMostMove;
12763
12764     if (target > limit) target = limit;
12765
12766     if (target > 0 && moveList[target - 1][0]) {
12767         int fromX, fromY, toX, toY;
12768         toX = moveList[target - 1][2] - AAA;
12769         toY = moveList[target - 1][3] - ONE;
12770         if (moveList[target - 1][1] == '@') {
12771             if (appData.highlightLastMove) {
12772                 SetHighlights(-1, -1, toX, toY);
12773             }
12774         } else {
12775             fromX = moveList[target - 1][0] - AAA;
12776             fromY = moveList[target - 1][1] - ONE;
12777             if (target == currentMove + 1) {
12778                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12779             }
12780             if (appData.highlightLastMove) {
12781                 SetHighlights(fromX, fromY, toX, toY);
12782             }
12783         }
12784     }
12785     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12786         gameMode == Training || gameMode == PlayFromGameFile ||
12787         gameMode == AnalyzeFile) {
12788         while (currentMove < target) {
12789             SendMoveToProgram(currentMove++, &first);
12790         }
12791     } else {
12792         currentMove = target;
12793     }
12794
12795     if (gameMode == EditGame || gameMode == EndOfGame) {
12796         whiteTimeRemaining = timeRemaining[0][currentMove];
12797         blackTimeRemaining = timeRemaining[1][currentMove];
12798     }
12799     DisplayBothClocks();
12800     DisplayMove(currentMove - 1);
12801     DrawPosition(FALSE, boards[currentMove]);
12802     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12803     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12804         DisplayComment(currentMove - 1, commentList[currentMove]);
12805     }
12806 }
12807
12808
12809 void
12810 ForwardEvent()
12811 {
12812     if (gameMode == IcsExamining && !pausing) {
12813         SendToICS(ics_prefix);
12814         SendToICS("forward\n");
12815     } else {
12816         ForwardInner(currentMove + 1);
12817     }
12818 }
12819
12820 void
12821 ToEndEvent()
12822 {
12823     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12824         /* to optimze, we temporarily turn off analysis mode while we feed
12825          * the remaining moves to the engine. Otherwise we get analysis output
12826          * after each move.
12827          */
12828         if (first.analysisSupport) {
12829           SendToProgram("exit\nforce\n", &first);
12830           first.analyzing = FALSE;
12831         }
12832     }
12833
12834     if (gameMode == IcsExamining && !pausing) {
12835         SendToICS(ics_prefix);
12836         SendToICS("forward 999999\n");
12837     } else {
12838         ForwardInner(forwardMostMove);
12839     }
12840
12841     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12842         /* we have fed all the moves, so reactivate analysis mode */
12843         SendToProgram("analyze\n", &first);
12844         first.analyzing = TRUE;
12845         /*first.maybeThinking = TRUE;*/
12846         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12847     }
12848 }
12849
12850 void
12851 BackwardInner(target)
12852      int target;
12853 {
12854     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12855
12856     if (appData.debugMode)
12857         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12858                 target, currentMove, forwardMostMove);
12859
12860     if (gameMode == EditPosition) return;
12861     if (currentMove <= backwardMostMove) {
12862         ClearHighlights();
12863         DrawPosition(full_redraw, boards[currentMove]);
12864         return;
12865     }
12866     if (gameMode == PlayFromGameFile && !pausing)
12867       PauseEvent();
12868
12869     if (moveList[target][0]) {
12870         int fromX, fromY, toX, toY;
12871         toX = moveList[target][2] - AAA;
12872         toY = moveList[target][3] - ONE;
12873         if (moveList[target][1] == '@') {
12874             if (appData.highlightLastMove) {
12875                 SetHighlights(-1, -1, toX, toY);
12876             }
12877         } else {
12878             fromX = moveList[target][0] - AAA;
12879             fromY = moveList[target][1] - ONE;
12880             if (target == currentMove - 1) {
12881                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12882             }
12883             if (appData.highlightLastMove) {
12884                 SetHighlights(fromX, fromY, toX, toY);
12885             }
12886         }
12887     }
12888     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12889         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12890         while (currentMove > target) {
12891             SendToProgram("undo\n", &first);
12892             currentMove--;
12893         }
12894     } else {
12895         currentMove = target;
12896     }
12897
12898     if (gameMode == EditGame || gameMode == EndOfGame) {
12899         whiteTimeRemaining = timeRemaining[0][currentMove];
12900         blackTimeRemaining = timeRemaining[1][currentMove];
12901     }
12902     DisplayBothClocks();
12903     DisplayMove(currentMove - 1);
12904     DrawPosition(full_redraw, boards[currentMove]);
12905     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12906     // [HGM] PV info: routine tests if comment empty
12907     DisplayComment(currentMove - 1, commentList[currentMove]);
12908 }
12909
12910 void
12911 BackwardEvent()
12912 {
12913     if (gameMode == IcsExamining && !pausing) {
12914         SendToICS(ics_prefix);
12915         SendToICS("backward\n");
12916     } else {
12917         BackwardInner(currentMove - 1);
12918     }
12919 }
12920
12921 void
12922 ToStartEvent()
12923 {
12924     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12925         /* to optimize, we temporarily turn off analysis mode while we undo
12926          * all the moves. Otherwise we get analysis output after each undo.
12927          */
12928         if (first.analysisSupport) {
12929           SendToProgram("exit\nforce\n", &first);
12930           first.analyzing = FALSE;
12931         }
12932     }
12933
12934     if (gameMode == IcsExamining && !pausing) {
12935         SendToICS(ics_prefix);
12936         SendToICS("backward 999999\n");
12937     } else {
12938         BackwardInner(backwardMostMove);
12939     }
12940
12941     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12942         /* we have fed all the moves, so reactivate analysis mode */
12943         SendToProgram("analyze\n", &first);
12944         first.analyzing = TRUE;
12945         /*first.maybeThinking = TRUE;*/
12946         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12947     }
12948 }
12949
12950 void
12951 ToNrEvent(int to)
12952 {
12953   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12954   if (to >= forwardMostMove) to = forwardMostMove;
12955   if (to <= backwardMostMove) to = backwardMostMove;
12956   if (to < currentMove) {
12957     BackwardInner(to);
12958   } else {
12959     ForwardInner(to);
12960   }
12961 }
12962
12963 void
12964 RevertEvent(Boolean annotate)
12965 {
12966     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12967         return;
12968     }
12969     if (gameMode != IcsExamining) {
12970         DisplayError(_("You are not examining a game"), 0);
12971         return;
12972     }
12973     if (pausing) {
12974         DisplayError(_("You can't revert while pausing"), 0);
12975         return;
12976     }
12977     SendToICS(ics_prefix);
12978     SendToICS("revert\n");
12979 }
12980
12981 void
12982 RetractMoveEvent()
12983 {
12984     switch (gameMode) {
12985       case MachinePlaysWhite:
12986       case MachinePlaysBlack:
12987         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12988             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12989             return;
12990         }
12991         if (forwardMostMove < 2) return;
12992         currentMove = forwardMostMove = forwardMostMove - 2;
12993         whiteTimeRemaining = timeRemaining[0][currentMove];
12994         blackTimeRemaining = timeRemaining[1][currentMove];
12995         DisplayBothClocks();
12996         DisplayMove(currentMove - 1);
12997         ClearHighlights();/*!! could figure this out*/
12998         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12999         SendToProgram("remove\n", &first);
13000         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13001         break;
13002
13003       case BeginningOfGame:
13004       default:
13005         break;
13006
13007       case IcsPlayingWhite:
13008       case IcsPlayingBlack:
13009         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13010             SendToICS(ics_prefix);
13011             SendToICS("takeback 2\n");
13012         } else {
13013             SendToICS(ics_prefix);
13014             SendToICS("takeback 1\n");
13015         }
13016         break;
13017     }
13018 }
13019
13020 void
13021 MoveNowEvent()
13022 {
13023     ChessProgramState *cps;
13024
13025     switch (gameMode) {
13026       case MachinePlaysWhite:
13027         if (!WhiteOnMove(forwardMostMove)) {
13028             DisplayError(_("It is your turn"), 0);
13029             return;
13030         }
13031         cps = &first;
13032         break;
13033       case MachinePlaysBlack:
13034         if (WhiteOnMove(forwardMostMove)) {
13035             DisplayError(_("It is your turn"), 0);
13036             return;
13037         }
13038         cps = &first;
13039         break;
13040       case TwoMachinesPlay:
13041         if (WhiteOnMove(forwardMostMove) ==
13042             (first.twoMachinesColor[0] == 'w')) {
13043             cps = &first;
13044         } else {
13045             cps = &second;
13046         }
13047         break;
13048       case BeginningOfGame:
13049       default:
13050         return;
13051     }
13052     SendToProgram("?\n", cps);
13053 }
13054
13055 void
13056 TruncateGameEvent()
13057 {
13058     EditGameEvent();
13059     if (gameMode != EditGame) return;
13060     TruncateGame();
13061 }
13062
13063 void
13064 TruncateGame()
13065 {
13066     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13067     if (forwardMostMove > currentMove) {
13068         if (gameInfo.resultDetails != NULL) {
13069             free(gameInfo.resultDetails);
13070             gameInfo.resultDetails = NULL;
13071             gameInfo.result = GameUnfinished;
13072         }
13073         forwardMostMove = currentMove;
13074         HistorySet(parseList, backwardMostMove, forwardMostMove,
13075                    currentMove-1);
13076     }
13077 }
13078
13079 void
13080 HintEvent()
13081 {
13082     if (appData.noChessProgram) return;
13083     switch (gameMode) {
13084       case MachinePlaysWhite:
13085         if (WhiteOnMove(forwardMostMove)) {
13086             DisplayError(_("Wait until your turn"), 0);
13087             return;
13088         }
13089         break;
13090       case BeginningOfGame:
13091       case MachinePlaysBlack:
13092         if (!WhiteOnMove(forwardMostMove)) {
13093             DisplayError(_("Wait until your turn"), 0);
13094             return;
13095         }
13096         break;
13097       default:
13098         DisplayError(_("No hint available"), 0);
13099         return;
13100     }
13101     SendToProgram("hint\n", &first);
13102     hintRequested = TRUE;
13103 }
13104
13105 void
13106 BookEvent()
13107 {
13108     if (appData.noChessProgram) return;
13109     switch (gameMode) {
13110       case MachinePlaysWhite:
13111         if (WhiteOnMove(forwardMostMove)) {
13112             DisplayError(_("Wait until your turn"), 0);
13113             return;
13114         }
13115         break;
13116       case BeginningOfGame:
13117       case MachinePlaysBlack:
13118         if (!WhiteOnMove(forwardMostMove)) {
13119             DisplayError(_("Wait until your turn"), 0);
13120             return;
13121         }
13122         break;
13123       case EditPosition:
13124         EditPositionDone(TRUE);
13125         break;
13126       case TwoMachinesPlay:
13127         return;
13128       default:
13129         break;
13130     }
13131     SendToProgram("bk\n", &first);
13132     bookOutput[0] = NULLCHAR;
13133     bookRequested = TRUE;
13134 }
13135
13136 void
13137 AboutGameEvent()
13138 {
13139     char *tags = PGNTags(&gameInfo);
13140     TagsPopUp(tags, CmailMsg());
13141     free(tags);
13142 }
13143
13144 /* end button procedures */
13145
13146 void
13147 PrintPosition(fp, move)
13148      FILE *fp;
13149      int move;
13150 {
13151     int i, j;
13152
13153     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13154         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13155             char c = PieceToChar(boards[move][i][j]);
13156             fputc(c == 'x' ? '.' : c, fp);
13157             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13158         }
13159     }
13160     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13161       fprintf(fp, "white to play\n");
13162     else
13163       fprintf(fp, "black to play\n");
13164 }
13165
13166 void
13167 PrintOpponents(fp)
13168      FILE *fp;
13169 {
13170     if (gameInfo.white != NULL) {
13171         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13172     } else {
13173         fprintf(fp, "\n");
13174     }
13175 }
13176
13177 /* Find last component of program's own name, using some heuristics */
13178 void
13179 TidyProgramName(prog, host, buf)
13180      char *prog, *host, buf[MSG_SIZ];
13181 {
13182     char *p, *q;
13183     int local = (strcmp(host, "localhost") == 0);
13184     while (!local && (p = strchr(prog, ';')) != NULL) {
13185         p++;
13186         while (*p == ' ') p++;
13187         prog = p;
13188     }
13189     if (*prog == '"' || *prog == '\'') {
13190         q = strchr(prog + 1, *prog);
13191     } else {
13192         q = strchr(prog, ' ');
13193     }
13194     if (q == NULL) q = prog + strlen(prog);
13195     p = q;
13196     while (p >= prog && *p != '/' && *p != '\\') p--;
13197     p++;
13198     if(p == prog && *p == '"') p++;
13199     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13200     memcpy(buf, p, q - p);
13201     buf[q - p] = NULLCHAR;
13202     if (!local) {
13203         strcat(buf, "@");
13204         strcat(buf, host);
13205     }
13206 }
13207
13208 char *
13209 TimeControlTagValue()
13210 {
13211     char buf[MSG_SIZ];
13212     if (!appData.clockMode) {
13213       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13214     } else if (movesPerSession > 0) {
13215       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13216     } else if (timeIncrement == 0) {
13217       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13218     } else {
13219       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13220     }
13221     return StrSave(buf);
13222 }
13223
13224 void
13225 SetGameInfo()
13226 {
13227     /* This routine is used only for certain modes */
13228     VariantClass v = gameInfo.variant;
13229     ChessMove r = GameUnfinished;
13230     char *p = NULL;
13231
13232     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13233         r = gameInfo.result;
13234         p = gameInfo.resultDetails;
13235         gameInfo.resultDetails = NULL;
13236     }
13237     ClearGameInfo(&gameInfo);
13238     gameInfo.variant = v;
13239
13240     switch (gameMode) {
13241       case MachinePlaysWhite:
13242         gameInfo.event = StrSave( appData.pgnEventHeader );
13243         gameInfo.site = StrSave(HostName());
13244         gameInfo.date = PGNDate();
13245         gameInfo.round = StrSave("-");
13246         gameInfo.white = StrSave(first.tidy);
13247         gameInfo.black = StrSave(UserName());
13248         gameInfo.timeControl = TimeControlTagValue();
13249         break;
13250
13251       case MachinePlaysBlack:
13252         gameInfo.event = StrSave( appData.pgnEventHeader );
13253         gameInfo.site = StrSave(HostName());
13254         gameInfo.date = PGNDate();
13255         gameInfo.round = StrSave("-");
13256         gameInfo.white = StrSave(UserName());
13257         gameInfo.black = StrSave(first.tidy);
13258         gameInfo.timeControl = TimeControlTagValue();
13259         break;
13260
13261       case TwoMachinesPlay:
13262         gameInfo.event = StrSave( appData.pgnEventHeader );
13263         gameInfo.site = StrSave(HostName());
13264         gameInfo.date = PGNDate();
13265         if (matchGame > 0) {
13266             char buf[MSG_SIZ];
13267             snprintf(buf, MSG_SIZ, "%d", matchGame);
13268             gameInfo.round = StrSave(buf);
13269         } else {
13270             gameInfo.round = StrSave("-");
13271         }
13272         if (first.twoMachinesColor[0] == 'w') {
13273             gameInfo.white = StrSave(first.tidy);
13274             gameInfo.black = StrSave(second.tidy);
13275         } else {
13276             gameInfo.white = StrSave(second.tidy);
13277             gameInfo.black = StrSave(first.tidy);
13278         }
13279         gameInfo.timeControl = TimeControlTagValue();
13280         break;
13281
13282       case EditGame:
13283         gameInfo.event = StrSave("Edited game");
13284         gameInfo.site = StrSave(HostName());
13285         gameInfo.date = PGNDate();
13286         gameInfo.round = StrSave("-");
13287         gameInfo.white = StrSave("-");
13288         gameInfo.black = StrSave("-");
13289         gameInfo.result = r;
13290         gameInfo.resultDetails = p;
13291         break;
13292
13293       case EditPosition:
13294         gameInfo.event = StrSave("Edited position");
13295         gameInfo.site = StrSave(HostName());
13296         gameInfo.date = PGNDate();
13297         gameInfo.round = StrSave("-");
13298         gameInfo.white = StrSave("-");
13299         gameInfo.black = StrSave("-");
13300         break;
13301
13302       case IcsPlayingWhite:
13303       case IcsPlayingBlack:
13304       case IcsObserving:
13305       case IcsExamining:
13306         break;
13307
13308       case PlayFromGameFile:
13309         gameInfo.event = StrSave("Game from non-PGN file");
13310         gameInfo.site = StrSave(HostName());
13311         gameInfo.date = PGNDate();
13312         gameInfo.round = StrSave("-");
13313         gameInfo.white = StrSave("?");
13314         gameInfo.black = StrSave("?");
13315         break;
13316
13317       default:
13318         break;
13319     }
13320 }
13321
13322 void
13323 ReplaceComment(index, text)
13324      int index;
13325      char *text;
13326 {
13327     int len;
13328     char *p;
13329     float score;
13330
13331     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13332        pvInfoList[index-1].depth == len &&
13333        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13334        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13335     while (*text == '\n') text++;
13336     len = strlen(text);
13337     while (len > 0 && text[len - 1] == '\n') len--;
13338
13339     if (commentList[index] != NULL)
13340       free(commentList[index]);
13341
13342     if (len == 0) {
13343         commentList[index] = NULL;
13344         return;
13345     }
13346   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13347       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13348       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13349     commentList[index] = (char *) malloc(len + 2);
13350     strncpy(commentList[index], text, len);
13351     commentList[index][len] = '\n';
13352     commentList[index][len + 1] = NULLCHAR;
13353   } else {
13354     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13355     char *p;
13356     commentList[index] = (char *) malloc(len + 7);
13357     safeStrCpy(commentList[index], "{\n", 3);
13358     safeStrCpy(commentList[index]+2, text, len+1);
13359     commentList[index][len+2] = NULLCHAR;
13360     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13361     strcat(commentList[index], "\n}\n");
13362   }
13363 }
13364
13365 void
13366 CrushCRs(text)
13367      char *text;
13368 {
13369   char *p = text;
13370   char *q = text;
13371   char ch;
13372
13373   do {
13374     ch = *p++;
13375     if (ch == '\r') continue;
13376     *q++ = ch;
13377   } while (ch != '\0');
13378 }
13379
13380 void
13381 AppendComment(index, text, addBraces)
13382      int index;
13383      char *text;
13384      Boolean addBraces; // [HGM] braces: tells if we should add {}
13385 {
13386     int oldlen, len;
13387     char *old;
13388
13389 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13390     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13391
13392     CrushCRs(text);
13393     while (*text == '\n') text++;
13394     len = strlen(text);
13395     while (len > 0 && text[len - 1] == '\n') len--;
13396
13397     if (len == 0) return;
13398
13399     if (commentList[index] != NULL) {
13400         old = commentList[index];
13401         oldlen = strlen(old);
13402         while(commentList[index][oldlen-1] ==  '\n')
13403           commentList[index][--oldlen] = NULLCHAR;
13404         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13405         safeStrCpy(commentList[index], old, oldlen + len + 6);
13406         free(old);
13407         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13408         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13409           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13410           while (*text == '\n') { text++; len--; }
13411           commentList[index][--oldlen] = NULLCHAR;
13412       }
13413         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13414         else          strcat(commentList[index], "\n");
13415         strcat(commentList[index], text);
13416         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13417         else          strcat(commentList[index], "\n");
13418     } else {
13419         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13420         if(addBraces)
13421           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13422         else commentList[index][0] = NULLCHAR;
13423         strcat(commentList[index], text);
13424         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13425         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13426     }
13427 }
13428
13429 static char * FindStr( char * text, char * sub_text )
13430 {
13431     char * result = strstr( text, sub_text );
13432
13433     if( result != NULL ) {
13434         result += strlen( sub_text );
13435     }
13436
13437     return result;
13438 }
13439
13440 /* [AS] Try to extract PV info from PGN comment */
13441 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13442 char *GetInfoFromComment( int index, char * text )
13443 {
13444     char * sep = text, *p;
13445
13446     if( text != NULL && index > 0 ) {
13447         int score = 0;
13448         int depth = 0;
13449         int time = -1, sec = 0, deci;
13450         char * s_eval = FindStr( text, "[%eval " );
13451         char * s_emt = FindStr( text, "[%emt " );
13452
13453         if( s_eval != NULL || s_emt != NULL ) {
13454             /* New style */
13455             char delim;
13456
13457             if( s_eval != NULL ) {
13458                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13459                     return text;
13460                 }
13461
13462                 if( delim != ']' ) {
13463                     return text;
13464                 }
13465             }
13466
13467             if( s_emt != NULL ) {
13468             }
13469                 return text;
13470         }
13471         else {
13472             /* We expect something like: [+|-]nnn.nn/dd */
13473             int score_lo = 0;
13474
13475             if(*text != '{') return text; // [HGM] braces: must be normal comment
13476
13477             sep = strchr( text, '/' );
13478             if( sep == NULL || sep < (text+4) ) {
13479                 return text;
13480             }
13481
13482             p = text;
13483             if(p[1] == '(') { // comment starts with PV
13484                p = strchr(p, ')'); // locate end of PV
13485                if(p == NULL || sep < p+5) return text;
13486                // at this point we have something like "{(.*) +0.23/6 ..."
13487                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13488                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13489                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13490             }
13491             time = -1; sec = -1; deci = -1;
13492             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13493                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13494                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13495                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13496                 return text;
13497             }
13498
13499             if( score_lo < 0 || score_lo >= 100 ) {
13500                 return text;
13501             }
13502
13503             if(sec >= 0) time = 600*time + 10*sec; else
13504             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13505
13506             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13507
13508             /* [HGM] PV time: now locate end of PV info */
13509             while( *++sep >= '0' && *sep <= '9'); // strip depth
13510             if(time >= 0)
13511             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13512             if(sec >= 0)
13513             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13514             if(deci >= 0)
13515             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13516             while(*sep == ' ') sep++;
13517         }
13518
13519         if( depth <= 0 ) {
13520             return text;
13521         }
13522
13523         if( time < 0 ) {
13524             time = -1;
13525         }
13526
13527         pvInfoList[index-1].depth = depth;
13528         pvInfoList[index-1].score = score;
13529         pvInfoList[index-1].time  = 10*time; // centi-sec
13530         if(*sep == '}') *sep = 0; else *--sep = '{';
13531         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13532     }
13533     return sep;
13534 }
13535
13536 void
13537 SendToProgram(message, cps)
13538      char *message;
13539      ChessProgramState *cps;
13540 {
13541     int count, outCount, error;
13542     char buf[MSG_SIZ];
13543
13544     if (cps->pr == NULL) return;
13545     Attention(cps);
13546
13547     if (appData.debugMode) {
13548         TimeMark now;
13549         GetTimeMark(&now);
13550         fprintf(debugFP, "%ld >%-6s: %s",
13551                 SubtractTimeMarks(&now, &programStartTime),
13552                 cps->which, message);
13553     }
13554
13555     count = strlen(message);
13556     outCount = OutputToProcess(cps->pr, message, count, &error);
13557     if (outCount < count && !exiting
13558                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13559       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13560         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13561             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13562                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13563                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13564             } else {
13565                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13566             }
13567             gameInfo.resultDetails = StrSave(buf);
13568         }
13569         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13570     }
13571 }
13572
13573 void
13574 ReceiveFromProgram(isr, closure, message, count, error)
13575      InputSourceRef isr;
13576      VOIDSTAR closure;
13577      char *message;
13578      int count;
13579      int error;
13580 {
13581     char *end_str;
13582     char buf[MSG_SIZ];
13583     ChessProgramState *cps = (ChessProgramState *)closure;
13584
13585     if (isr != cps->isr) return; /* Killed intentionally */
13586     if (count <= 0) {
13587         if (count == 0) {
13588             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13589                     cps->which, cps->program);
13590         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13591                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13592                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13593                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13594                 } else {
13595                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13596                 }
13597                 gameInfo.resultDetails = StrSave(buf);
13598             }
13599             RemoveInputSource(cps->isr);
13600             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13601         } else {
13602             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13603                     cps->which, cps->program);
13604             RemoveInputSource(cps->isr);
13605
13606             /* [AS] Program is misbehaving badly... kill it */
13607             if( count == -2 ) {
13608                 DestroyChildProcess( cps->pr, 9 );
13609                 cps->pr = NoProc;
13610             }
13611
13612             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13613         }
13614         return;
13615     }
13616
13617     if ((end_str = strchr(message, '\r')) != NULL)
13618       *end_str = NULLCHAR;
13619     if ((end_str = strchr(message, '\n')) != NULL)
13620       *end_str = NULLCHAR;
13621
13622     if (appData.debugMode) {
13623         TimeMark now; int print = 1;
13624         char *quote = ""; char c; int i;
13625
13626         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13627                 char start = message[0];
13628                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13629                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13630                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13631                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13632                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13633                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13634                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13635                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13636                    sscanf(message, "hint: %c", &c)!=1 && 
13637                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13638                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13639                     print = (appData.engineComments >= 2);
13640                 }
13641                 message[0] = start; // restore original message
13642         }
13643         if(print) {
13644                 GetTimeMark(&now);
13645                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13646                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13647                         quote,
13648                         message);
13649         }
13650     }
13651
13652     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13653     if (appData.icsEngineAnalyze) {
13654         if (strstr(message, "whisper") != NULL ||
13655              strstr(message, "kibitz") != NULL ||
13656             strstr(message, "tellics") != NULL) return;
13657     }
13658
13659     HandleMachineMove(message, cps);
13660 }
13661
13662
13663 void
13664 SendTimeControl(cps, mps, tc, inc, sd, st)
13665      ChessProgramState *cps;
13666      int mps, inc, sd, st;
13667      long tc;
13668 {
13669     char buf[MSG_SIZ];
13670     int seconds;
13671
13672     if( timeControl_2 > 0 ) {
13673         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13674             tc = timeControl_2;
13675         }
13676     }
13677     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13678     inc /= cps->timeOdds;
13679     st  /= cps->timeOdds;
13680
13681     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13682
13683     if (st > 0) {
13684       /* Set exact time per move, normally using st command */
13685       if (cps->stKludge) {
13686         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13687         seconds = st % 60;
13688         if (seconds == 0) {
13689           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13690         } else {
13691           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13692         }
13693       } else {
13694         snprintf(buf, MSG_SIZ, "st %d\n", st);
13695       }
13696     } else {
13697       /* Set conventional or incremental time control, using level command */
13698       if (seconds == 0) {
13699         /* Note old gnuchess bug -- minutes:seconds used to not work.
13700            Fixed in later versions, but still avoid :seconds
13701            when seconds is 0. */
13702         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13703       } else {
13704         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13705                  seconds, inc/1000.);
13706       }
13707     }
13708     SendToProgram(buf, cps);
13709
13710     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13711     /* Orthogonally, limit search to given depth */
13712     if (sd > 0) {
13713       if (cps->sdKludge) {
13714         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13715       } else {
13716         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13717       }
13718       SendToProgram(buf, cps);
13719     }
13720
13721     if(cps->nps > 0) { /* [HGM] nps */
13722         if(cps->supportsNPS == FALSE)
13723           cps->nps = -1; // don't use if engine explicitly says not supported!
13724         else {
13725           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13726           SendToProgram(buf, cps);
13727         }
13728     }
13729 }
13730
13731 ChessProgramState *WhitePlayer()
13732 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13733 {
13734     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13735        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13736         return &second;
13737     return &first;
13738 }
13739
13740 void
13741 SendTimeRemaining(cps, machineWhite)
13742      ChessProgramState *cps;
13743      int /*boolean*/ machineWhite;
13744 {
13745     char message[MSG_SIZ];
13746     long time, otime;
13747
13748     /* Note: this routine must be called when the clocks are stopped
13749        or when they have *just* been set or switched; otherwise
13750        it will be off by the time since the current tick started.
13751     */
13752     if (machineWhite) {
13753         time = whiteTimeRemaining / 10;
13754         otime = blackTimeRemaining / 10;
13755     } else {
13756         time = blackTimeRemaining / 10;
13757         otime = whiteTimeRemaining / 10;
13758     }
13759     /* [HGM] translate opponent's time by time-odds factor */
13760     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13761     if (appData.debugMode) {
13762         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13763     }
13764
13765     if (time <= 0) time = 1;
13766     if (otime <= 0) otime = 1;
13767
13768     snprintf(message, MSG_SIZ, "time %ld\n", time);
13769     SendToProgram(message, cps);
13770
13771     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13772     SendToProgram(message, cps);
13773 }
13774
13775 int
13776 BoolFeature(p, name, loc, cps)
13777      char **p;
13778      char *name;
13779      int *loc;
13780      ChessProgramState *cps;
13781 {
13782   char buf[MSG_SIZ];
13783   int len = strlen(name);
13784   int val;
13785
13786   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13787     (*p) += len + 1;
13788     sscanf(*p, "%d", &val);
13789     *loc = (val != 0);
13790     while (**p && **p != ' ')
13791       (*p)++;
13792     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13793     SendToProgram(buf, cps);
13794     return TRUE;
13795   }
13796   return FALSE;
13797 }
13798
13799 int
13800 IntFeature(p, name, loc, cps)
13801      char **p;
13802      char *name;
13803      int *loc;
13804      ChessProgramState *cps;
13805 {
13806   char buf[MSG_SIZ];
13807   int len = strlen(name);
13808   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13809     (*p) += len + 1;
13810     sscanf(*p, "%d", loc);
13811     while (**p && **p != ' ') (*p)++;
13812     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13813     SendToProgram(buf, cps);
13814     return TRUE;
13815   }
13816   return FALSE;
13817 }
13818
13819 int
13820 StringFeature(p, name, loc, cps)
13821      char **p;
13822      char *name;
13823      char loc[];
13824      ChessProgramState *cps;
13825 {
13826   char buf[MSG_SIZ];
13827   int len = strlen(name);
13828   if (strncmp((*p), name, len) == 0
13829       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13830     (*p) += len + 2;
13831     sscanf(*p, "%[^\"]", loc);
13832     while (**p && **p != '\"') (*p)++;
13833     if (**p == '\"') (*p)++;
13834     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13835     SendToProgram(buf, cps);
13836     return TRUE;
13837   }
13838   return FALSE;
13839 }
13840
13841 int
13842 ParseOption(Option *opt, ChessProgramState *cps)
13843 // [HGM] options: process the string that defines an engine option, and determine
13844 // name, type, default value, and allowed value range
13845 {
13846         char *p, *q, buf[MSG_SIZ];
13847         int n, min = (-1)<<31, max = 1<<31, def;
13848
13849         if(p = strstr(opt->name, " -spin ")) {
13850             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13851             if(max < min) max = min; // enforce consistency
13852             if(def < min) def = min;
13853             if(def > max) def = max;
13854             opt->value = def;
13855             opt->min = min;
13856             opt->max = max;
13857             opt->type = Spin;
13858         } else if((p = strstr(opt->name, " -slider "))) {
13859             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13860             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13861             if(max < min) max = min; // enforce consistency
13862             if(def < min) def = min;
13863             if(def > max) def = max;
13864             opt->value = def;
13865             opt->min = min;
13866             opt->max = max;
13867             opt->type = Spin; // Slider;
13868         } else if((p = strstr(opt->name, " -string "))) {
13869             opt->textValue = p+9;
13870             opt->type = TextBox;
13871         } else if((p = strstr(opt->name, " -file "))) {
13872             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13873             opt->textValue = p+7;
13874             opt->type = TextBox; // FileName;
13875         } else if((p = strstr(opt->name, " -path "))) {
13876             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13877             opt->textValue = p+7;
13878             opt->type = TextBox; // PathName;
13879         } else if(p = strstr(opt->name, " -check ")) {
13880             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13881             opt->value = (def != 0);
13882             opt->type = CheckBox;
13883         } else if(p = strstr(opt->name, " -combo ")) {
13884             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13885             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13886             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13887             opt->value = n = 0;
13888             while(q = StrStr(q, " /// ")) {
13889                 n++; *q = 0;    // count choices, and null-terminate each of them
13890                 q += 5;
13891                 if(*q == '*') { // remember default, which is marked with * prefix
13892                     q++;
13893                     opt->value = n;
13894                 }
13895                 cps->comboList[cps->comboCnt++] = q;
13896             }
13897             cps->comboList[cps->comboCnt++] = NULL;
13898             opt->max = n + 1;
13899             opt->type = ComboBox;
13900         } else if(p = strstr(opt->name, " -button")) {
13901             opt->type = Button;
13902         } else if(p = strstr(opt->name, " -save")) {
13903             opt->type = SaveButton;
13904         } else return FALSE;
13905         *p = 0; // terminate option name
13906         // now look if the command-line options define a setting for this engine option.
13907         if(cps->optionSettings && cps->optionSettings[0])
13908             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13909         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13910           snprintf(buf, MSG_SIZ, "option %s", p);
13911                 if(p = strstr(buf, ",")) *p = 0;
13912                 if(q = strchr(buf, '=')) switch(opt->type) {
13913                     case ComboBox:
13914                         for(n=0; n<opt->max; n++)
13915                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13916                         break;
13917                     case TextBox:
13918                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13919                         break;
13920                     case Spin:
13921                     case CheckBox:
13922                         opt->value = atoi(q+1);
13923                     default:
13924                         break;
13925                 }
13926                 strcat(buf, "\n");
13927                 SendToProgram(buf, cps);
13928         }
13929         return TRUE;
13930 }
13931
13932 void
13933 FeatureDone(cps, val)
13934      ChessProgramState* cps;
13935      int val;
13936 {
13937   DelayedEventCallback cb = GetDelayedEvent();
13938   if ((cb == InitBackEnd3 && cps == &first) ||
13939       (cb == SettingsMenuIfReady && cps == &second) ||
13940       (cb == TwoMachinesEventIfReady && cps == &second)) {
13941     CancelDelayedEvent();
13942     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13943   }
13944   cps->initDone = val;
13945 }
13946
13947 /* Parse feature command from engine */
13948 void
13949 ParseFeatures(args, cps)
13950      char* args;
13951      ChessProgramState *cps;
13952 {
13953   char *p = args;
13954   char *q;
13955   int val;
13956   char buf[MSG_SIZ];
13957
13958   for (;;) {
13959     while (*p == ' ') p++;
13960     if (*p == NULLCHAR) return;
13961
13962     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13963     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13964     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13965     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13966     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13967     if (BoolFeature(&p, "reuse", &val, cps)) {
13968       /* Engine can disable reuse, but can't enable it if user said no */
13969       if (!val) cps->reuse = FALSE;
13970       continue;
13971     }
13972     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13973     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13974       if (gameMode == TwoMachinesPlay) {
13975         DisplayTwoMachinesTitle();
13976       } else {
13977         DisplayTitle("");
13978       }
13979       continue;
13980     }
13981     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13982     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13983     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13984     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13985     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13986     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13987     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13988     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13989     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13990     if (IntFeature(&p, "done", &val, cps)) {
13991       FeatureDone(cps, val);
13992       continue;
13993     }
13994     /* Added by Tord: */
13995     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13996     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13997     /* End of additions by Tord */
13998
13999     /* [HGM] added features: */
14000     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14001     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14002     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14003     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14004     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14005     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14006     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14007         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14008           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14009             SendToProgram(buf, cps);
14010             continue;
14011         }
14012         if(cps->nrOptions >= MAX_OPTIONS) {
14013             cps->nrOptions--;
14014             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
14015             DisplayError(buf, 0);
14016         }
14017         continue;
14018     }
14019     /* End of additions by HGM */
14020
14021     /* unknown feature: complain and skip */
14022     q = p;
14023     while (*q && *q != '=') q++;
14024     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14025     SendToProgram(buf, cps);
14026     p = q;
14027     if (*p == '=') {
14028       p++;
14029       if (*p == '\"') {
14030         p++;
14031         while (*p && *p != '\"') p++;
14032         if (*p == '\"') p++;
14033       } else {
14034         while (*p && *p != ' ') p++;
14035       }
14036     }
14037   }
14038
14039 }
14040
14041 void
14042 PeriodicUpdatesEvent(newState)
14043      int newState;
14044 {
14045     if (newState == appData.periodicUpdates)
14046       return;
14047
14048     appData.periodicUpdates=newState;
14049
14050     /* Display type changes, so update it now */
14051 //    DisplayAnalysis();
14052
14053     /* Get the ball rolling again... */
14054     if (newState) {
14055         AnalysisPeriodicEvent(1);
14056         StartAnalysisClock();
14057     }
14058 }
14059
14060 void
14061 PonderNextMoveEvent(newState)
14062      int newState;
14063 {
14064     if (newState == appData.ponderNextMove) return;
14065     if (gameMode == EditPosition) EditPositionDone(TRUE);
14066     if (newState) {
14067         SendToProgram("hard\n", &first);
14068         if (gameMode == TwoMachinesPlay) {
14069             SendToProgram("hard\n", &second);
14070         }
14071     } else {
14072         SendToProgram("easy\n", &first);
14073         thinkOutput[0] = NULLCHAR;
14074         if (gameMode == TwoMachinesPlay) {
14075             SendToProgram("easy\n", &second);
14076         }
14077     }
14078     appData.ponderNextMove = newState;
14079 }
14080
14081 void
14082 NewSettingEvent(option, feature, command, value)
14083      char *command;
14084      int option, value, *feature;
14085 {
14086     char buf[MSG_SIZ];
14087
14088     if (gameMode == EditPosition) EditPositionDone(TRUE);
14089     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14090     if(feature == NULL || *feature) SendToProgram(buf, &first);
14091     if (gameMode == TwoMachinesPlay) {
14092         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14093     }
14094 }
14095
14096 void
14097 ShowThinkingEvent()
14098 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14099 {
14100     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14101     int newState = appData.showThinking
14102         // [HGM] thinking: other features now need thinking output as well
14103         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14104
14105     if (oldState == newState) return;
14106     oldState = newState;
14107     if (gameMode == EditPosition) EditPositionDone(TRUE);
14108     if (oldState) {
14109         SendToProgram("post\n", &first);
14110         if (gameMode == TwoMachinesPlay) {
14111             SendToProgram("post\n", &second);
14112         }
14113     } else {
14114         SendToProgram("nopost\n", &first);
14115         thinkOutput[0] = NULLCHAR;
14116         if (gameMode == TwoMachinesPlay) {
14117             SendToProgram("nopost\n", &second);
14118         }
14119     }
14120 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14121 }
14122
14123 void
14124 AskQuestionEvent(title, question, replyPrefix, which)
14125      char *title; char *question; char *replyPrefix; char *which;
14126 {
14127   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14128   if (pr == NoProc) return;
14129   AskQuestion(title, question, replyPrefix, pr);
14130 }
14131
14132 void
14133 DisplayMove(moveNumber)
14134      int moveNumber;
14135 {
14136     char message[MSG_SIZ];
14137     char res[MSG_SIZ];
14138     char cpThinkOutput[MSG_SIZ];
14139
14140     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14141
14142     if (moveNumber == forwardMostMove - 1 ||
14143         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14144
14145         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14146
14147         if (strchr(cpThinkOutput, '\n')) {
14148             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14149         }
14150     } else {
14151         *cpThinkOutput = NULLCHAR;
14152     }
14153
14154     /* [AS] Hide thinking from human user */
14155     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14156         *cpThinkOutput = NULLCHAR;
14157         if( thinkOutput[0] != NULLCHAR ) {
14158             int i;
14159
14160             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14161                 cpThinkOutput[i] = '.';
14162             }
14163             cpThinkOutput[i] = NULLCHAR;
14164             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14165         }
14166     }
14167
14168     if (moveNumber == forwardMostMove - 1 &&
14169         gameInfo.resultDetails != NULL) {
14170         if (gameInfo.resultDetails[0] == NULLCHAR) {
14171           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14172         } else {
14173           snprintf(res, MSG_SIZ, " {%s} %s",
14174                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14175         }
14176     } else {
14177         res[0] = NULLCHAR;
14178     }
14179
14180     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14181         DisplayMessage(res, cpThinkOutput);
14182     } else {
14183       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14184                 WhiteOnMove(moveNumber) ? " " : ".. ",
14185                 parseList[moveNumber], res);
14186         DisplayMessage(message, cpThinkOutput);
14187     }
14188 }
14189
14190 void
14191 DisplayComment(moveNumber, text)
14192      int moveNumber;
14193      char *text;
14194 {
14195     char title[MSG_SIZ];
14196     char buf[8000]; // comment can be long!
14197     int score, depth;
14198
14199     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14200       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14201     } else {
14202       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14203               WhiteOnMove(moveNumber) ? " " : ".. ",
14204               parseList[moveNumber]);
14205     }
14206     // [HGM] PV info: display PV info together with (or as) comment
14207     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14208       if(text == NULL) text = "";
14209       score = pvInfoList[moveNumber].score;
14210       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14211               depth, (pvInfoList[moveNumber].time+50)/100, text);
14212       text = buf;
14213     }
14214     if (text != NULL && (appData.autoDisplayComment || commentUp))
14215         CommentPopUp(title, text);
14216 }
14217
14218 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14219  * might be busy thinking or pondering.  It can be omitted if your
14220  * gnuchess is configured to stop thinking immediately on any user
14221  * input.  However, that gnuchess feature depends on the FIONREAD
14222  * ioctl, which does not work properly on some flavors of Unix.
14223  */
14224 void
14225 Attention(cps)
14226      ChessProgramState *cps;
14227 {
14228 #if ATTENTION
14229     if (!cps->useSigint) return;
14230     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14231     switch (gameMode) {
14232       case MachinePlaysWhite:
14233       case MachinePlaysBlack:
14234       case TwoMachinesPlay:
14235       case IcsPlayingWhite:
14236       case IcsPlayingBlack:
14237       case AnalyzeMode:
14238       case AnalyzeFile:
14239         /* Skip if we know it isn't thinking */
14240         if (!cps->maybeThinking) return;
14241         if (appData.debugMode)
14242           fprintf(debugFP, "Interrupting %s\n", cps->which);
14243         InterruptChildProcess(cps->pr);
14244         cps->maybeThinking = FALSE;
14245         break;
14246       default:
14247         break;
14248     }
14249 #endif /*ATTENTION*/
14250 }
14251
14252 int
14253 CheckFlags()
14254 {
14255     if (whiteTimeRemaining <= 0) {
14256         if (!whiteFlag) {
14257             whiteFlag = TRUE;
14258             if (appData.icsActive) {
14259                 if (appData.autoCallFlag &&
14260                     gameMode == IcsPlayingBlack && !blackFlag) {
14261                   SendToICS(ics_prefix);
14262                   SendToICS("flag\n");
14263                 }
14264             } else {
14265                 if (blackFlag) {
14266                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14267                 } else {
14268                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14269                     if (appData.autoCallFlag) {
14270                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14271                         return TRUE;
14272                     }
14273                 }
14274             }
14275         }
14276     }
14277     if (blackTimeRemaining <= 0) {
14278         if (!blackFlag) {
14279             blackFlag = TRUE;
14280             if (appData.icsActive) {
14281                 if (appData.autoCallFlag &&
14282                     gameMode == IcsPlayingWhite && !whiteFlag) {
14283                   SendToICS(ics_prefix);
14284                   SendToICS("flag\n");
14285                 }
14286             } else {
14287                 if (whiteFlag) {
14288                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14289                 } else {
14290                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14291                     if (appData.autoCallFlag) {
14292                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14293                         return TRUE;
14294                     }
14295                 }
14296             }
14297         }
14298     }
14299     return FALSE;
14300 }
14301
14302 void
14303 CheckTimeControl()
14304 {
14305     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14306         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14307
14308     /*
14309      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14310      */
14311     if ( !WhiteOnMove(forwardMostMove) ) {
14312         /* White made time control */
14313         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14314         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14315         /* [HGM] time odds: correct new time quota for time odds! */
14316                                             / WhitePlayer()->timeOdds;
14317         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14318     } else {
14319         lastBlack -= blackTimeRemaining;
14320         /* Black made time control */
14321         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14322                                             / WhitePlayer()->other->timeOdds;
14323         lastWhite = whiteTimeRemaining;
14324     }
14325 }
14326
14327 void
14328 DisplayBothClocks()
14329 {
14330     int wom = gameMode == EditPosition ?
14331       !blackPlaysFirst : WhiteOnMove(currentMove);
14332     DisplayWhiteClock(whiteTimeRemaining, wom);
14333     DisplayBlackClock(blackTimeRemaining, !wom);
14334 }
14335
14336
14337 /* Timekeeping seems to be a portability nightmare.  I think everyone
14338    has ftime(), but I'm really not sure, so I'm including some ifdefs
14339    to use other calls if you don't.  Clocks will be less accurate if
14340    you have neither ftime nor gettimeofday.
14341 */
14342
14343 /* VS 2008 requires the #include outside of the function */
14344 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14345 #include <sys/timeb.h>
14346 #endif
14347
14348 /* Get the current time as a TimeMark */
14349 void
14350 GetTimeMark(tm)
14351      TimeMark *tm;
14352 {
14353 #if HAVE_GETTIMEOFDAY
14354
14355     struct timeval timeVal;
14356     struct timezone timeZone;
14357
14358     gettimeofday(&timeVal, &timeZone);
14359     tm->sec = (long) timeVal.tv_sec;
14360     tm->ms = (int) (timeVal.tv_usec / 1000L);
14361
14362 #else /*!HAVE_GETTIMEOFDAY*/
14363 #if HAVE_FTIME
14364
14365 // include <sys/timeb.h> / moved to just above start of function
14366     struct timeb timeB;
14367
14368     ftime(&timeB);
14369     tm->sec = (long) timeB.time;
14370     tm->ms = (int) timeB.millitm;
14371
14372 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14373     tm->sec = (long) time(NULL);
14374     tm->ms = 0;
14375 #endif
14376 #endif
14377 }
14378
14379 /* Return the difference in milliseconds between two
14380    time marks.  We assume the difference will fit in a long!
14381 */
14382 long
14383 SubtractTimeMarks(tm2, tm1)
14384      TimeMark *tm2, *tm1;
14385 {
14386     return 1000L*(tm2->sec - tm1->sec) +
14387            (long) (tm2->ms - tm1->ms);
14388 }
14389
14390
14391 /*
14392  * Code to manage the game clocks.
14393  *
14394  * In tournament play, black starts the clock and then white makes a move.
14395  * We give the human user a slight advantage if he is playing white---the
14396  * clocks don't run until he makes his first move, so it takes zero time.
14397  * Also, we don't account for network lag, so we could get out of sync
14398  * with GNU Chess's clock -- but then, referees are always right.
14399  */
14400
14401 static TimeMark tickStartTM;
14402 static long intendedTickLength;
14403
14404 long
14405 NextTickLength(timeRemaining)
14406      long timeRemaining;
14407 {
14408     long nominalTickLength, nextTickLength;
14409
14410     if (timeRemaining > 0L && timeRemaining <= 10000L)
14411       nominalTickLength = 100L;
14412     else
14413       nominalTickLength = 1000L;
14414     nextTickLength = timeRemaining % nominalTickLength;
14415     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14416
14417     return nextTickLength;
14418 }
14419
14420 /* Adjust clock one minute up or down */
14421 void
14422 AdjustClock(Boolean which, int dir)
14423 {
14424     if(which) blackTimeRemaining += 60000*dir;
14425     else      whiteTimeRemaining += 60000*dir;
14426     DisplayBothClocks();
14427 }
14428
14429 /* Stop clocks and reset to a fresh time control */
14430 void
14431 ResetClocks()
14432 {
14433     (void) StopClockTimer();
14434     if (appData.icsActive) {
14435         whiteTimeRemaining = blackTimeRemaining = 0;
14436     } else if (searchTime) {
14437         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14438         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14439     } else { /* [HGM] correct new time quote for time odds */
14440         whiteTC = blackTC = fullTimeControlString;
14441         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14442         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14443     }
14444     if (whiteFlag || blackFlag) {
14445         DisplayTitle("");
14446         whiteFlag = blackFlag = FALSE;
14447     }
14448     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14449     DisplayBothClocks();
14450 }
14451
14452 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14453
14454 /* Decrement running clock by amount of time that has passed */
14455 void
14456 DecrementClocks()
14457 {
14458     long timeRemaining;
14459     long lastTickLength, fudge;
14460     TimeMark now;
14461
14462     if (!appData.clockMode) return;
14463     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14464
14465     GetTimeMark(&now);
14466
14467     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14468
14469     /* Fudge if we woke up a little too soon */
14470     fudge = intendedTickLength - lastTickLength;
14471     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14472
14473     if (WhiteOnMove(forwardMostMove)) {
14474         if(whiteNPS >= 0) lastTickLength = 0;
14475         timeRemaining = whiteTimeRemaining -= lastTickLength;
14476         if(timeRemaining < 0 && !appData.icsActive) {
14477             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14478             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14479                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14480                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14481             }
14482         }
14483         DisplayWhiteClock(whiteTimeRemaining - fudge,
14484                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14485     } else {
14486         if(blackNPS >= 0) lastTickLength = 0;
14487         timeRemaining = blackTimeRemaining -= lastTickLength;
14488         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14489             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14490             if(suddenDeath) {
14491                 blackStartMove = forwardMostMove;
14492                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14493             }
14494         }
14495         DisplayBlackClock(blackTimeRemaining - fudge,
14496                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14497     }
14498     if (CheckFlags()) return;
14499
14500     tickStartTM = now;
14501     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14502     StartClockTimer(intendedTickLength);
14503
14504     /* if the time remaining has fallen below the alarm threshold, sound the
14505      * alarm. if the alarm has sounded and (due to a takeback or time control
14506      * with increment) the time remaining has increased to a level above the
14507      * threshold, reset the alarm so it can sound again.
14508      */
14509
14510     if (appData.icsActive && appData.icsAlarm) {
14511
14512         /* make sure we are dealing with the user's clock */
14513         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14514                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14515            )) return;
14516
14517         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14518             alarmSounded = FALSE;
14519         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14520             PlayAlarmSound();
14521             alarmSounded = TRUE;
14522         }
14523     }
14524 }
14525
14526
14527 /* A player has just moved, so stop the previously running
14528    clock and (if in clock mode) start the other one.
14529    We redisplay both clocks in case we're in ICS mode, because
14530    ICS gives us an update to both clocks after every move.
14531    Note that this routine is called *after* forwardMostMove
14532    is updated, so the last fractional tick must be subtracted
14533    from the color that is *not* on move now.
14534 */
14535 void
14536 SwitchClocks(int newMoveNr)
14537 {
14538     long lastTickLength;
14539     TimeMark now;
14540     int flagged = FALSE;
14541
14542     GetTimeMark(&now);
14543
14544     if (StopClockTimer() && appData.clockMode) {
14545         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14546         if (!WhiteOnMove(forwardMostMove)) {
14547             if(blackNPS >= 0) lastTickLength = 0;
14548             blackTimeRemaining -= lastTickLength;
14549            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14550 //         if(pvInfoList[forwardMostMove].time == -1)
14551                  pvInfoList[forwardMostMove].time =               // use GUI time
14552                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14553         } else {
14554            if(whiteNPS >= 0) lastTickLength = 0;
14555            whiteTimeRemaining -= lastTickLength;
14556            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14557 //         if(pvInfoList[forwardMostMove].time == -1)
14558                  pvInfoList[forwardMostMove].time =
14559                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14560         }
14561         flagged = CheckFlags();
14562     }
14563     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14564     CheckTimeControl();
14565
14566     if (flagged || !appData.clockMode) return;
14567
14568     switch (gameMode) {
14569       case MachinePlaysBlack:
14570       case MachinePlaysWhite:
14571       case BeginningOfGame:
14572         if (pausing) return;
14573         break;
14574
14575       case EditGame:
14576       case PlayFromGameFile:
14577       case IcsExamining:
14578         return;
14579
14580       default:
14581         break;
14582     }
14583
14584     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14585         if(WhiteOnMove(forwardMostMove))
14586              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14587         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14588     }
14589
14590     tickStartTM = now;
14591     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14592       whiteTimeRemaining : blackTimeRemaining);
14593     StartClockTimer(intendedTickLength);
14594 }
14595
14596
14597 /* Stop both clocks */
14598 void
14599 StopClocks()
14600 {
14601     long lastTickLength;
14602     TimeMark now;
14603
14604     if (!StopClockTimer()) return;
14605     if (!appData.clockMode) return;
14606
14607     GetTimeMark(&now);
14608
14609     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14610     if (WhiteOnMove(forwardMostMove)) {
14611         if(whiteNPS >= 0) lastTickLength = 0;
14612         whiteTimeRemaining -= lastTickLength;
14613         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14614     } else {
14615         if(blackNPS >= 0) lastTickLength = 0;
14616         blackTimeRemaining -= lastTickLength;
14617         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14618     }
14619     CheckFlags();
14620 }
14621
14622 /* Start clock of player on move.  Time may have been reset, so
14623    if clock is already running, stop and restart it. */
14624 void
14625 StartClocks()
14626 {
14627     (void) StopClockTimer(); /* in case it was running already */
14628     DisplayBothClocks();
14629     if (CheckFlags()) return;
14630
14631     if (!appData.clockMode) return;
14632     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14633
14634     GetTimeMark(&tickStartTM);
14635     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14636       whiteTimeRemaining : blackTimeRemaining);
14637
14638    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14639     whiteNPS = blackNPS = -1;
14640     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14641        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14642         whiteNPS = first.nps;
14643     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14644        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14645         blackNPS = first.nps;
14646     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14647         whiteNPS = second.nps;
14648     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14649         blackNPS = second.nps;
14650     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14651
14652     StartClockTimer(intendedTickLength);
14653 }
14654
14655 char *
14656 TimeString(ms)
14657      long ms;
14658 {
14659     long second, minute, hour, day;
14660     char *sign = "";
14661     static char buf[32];
14662
14663     if (ms > 0 && ms <= 9900) {
14664       /* convert milliseconds to tenths, rounding up */
14665       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14666
14667       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14668       return buf;
14669     }
14670
14671     /* convert milliseconds to seconds, rounding up */
14672     /* use floating point to avoid strangeness of integer division
14673        with negative dividends on many machines */
14674     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14675
14676     if (second < 0) {
14677         sign = "-";
14678         second = -second;
14679     }
14680
14681     day = second / (60 * 60 * 24);
14682     second = second % (60 * 60 * 24);
14683     hour = second / (60 * 60);
14684     second = second % (60 * 60);
14685     minute = second / 60;
14686     second = second % 60;
14687
14688     if (day > 0)
14689       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14690               sign, day, hour, minute, second);
14691     else if (hour > 0)
14692       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14693     else
14694       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14695
14696     return buf;
14697 }
14698
14699
14700 /*
14701  * This is necessary because some C libraries aren't ANSI C compliant yet.
14702  */
14703 char *
14704 StrStr(string, match)
14705      char *string, *match;
14706 {
14707     int i, length;
14708
14709     length = strlen(match);
14710
14711     for (i = strlen(string) - length; i >= 0; i--, string++)
14712       if (!strncmp(match, string, length))
14713         return string;
14714
14715     return NULL;
14716 }
14717
14718 char *
14719 StrCaseStr(string, match)
14720      char *string, *match;
14721 {
14722     int i, j, length;
14723
14724     length = strlen(match);
14725
14726     for (i = strlen(string) - length; i >= 0; i--, string++) {
14727         for (j = 0; j < length; j++) {
14728             if (ToLower(match[j]) != ToLower(string[j]))
14729               break;
14730         }
14731         if (j == length) return string;
14732     }
14733
14734     return NULL;
14735 }
14736
14737 #ifndef _amigados
14738 int
14739 StrCaseCmp(s1, s2)
14740      char *s1, *s2;
14741 {
14742     char c1, c2;
14743
14744     for (;;) {
14745         c1 = ToLower(*s1++);
14746         c2 = ToLower(*s2++);
14747         if (c1 > c2) return 1;
14748         if (c1 < c2) return -1;
14749         if (c1 == NULLCHAR) return 0;
14750     }
14751 }
14752
14753
14754 int
14755 ToLower(c)
14756      int c;
14757 {
14758     return isupper(c) ? tolower(c) : c;
14759 }
14760
14761
14762 int
14763 ToUpper(c)
14764      int c;
14765 {
14766     return islower(c) ? toupper(c) : c;
14767 }
14768 #endif /* !_amigados    */
14769
14770 char *
14771 StrSave(s)
14772      char *s;
14773 {
14774   char *ret;
14775
14776   if ((ret = (char *) malloc(strlen(s) + 1)))
14777     {
14778       safeStrCpy(ret, s, strlen(s)+1);
14779     }
14780   return ret;
14781 }
14782
14783 char *
14784 StrSavePtr(s, savePtr)
14785      char *s, **savePtr;
14786 {
14787     if (*savePtr) {
14788         free(*savePtr);
14789     }
14790     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14791       safeStrCpy(*savePtr, s, strlen(s)+1);
14792     }
14793     return(*savePtr);
14794 }
14795
14796 char *
14797 PGNDate()
14798 {
14799     time_t clock;
14800     struct tm *tm;
14801     char buf[MSG_SIZ];
14802
14803     clock = time((time_t *)NULL);
14804     tm = localtime(&clock);
14805     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14806             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14807     return StrSave(buf);
14808 }
14809
14810
14811 char *
14812 PositionToFEN(move, overrideCastling)
14813      int move;
14814      char *overrideCastling;
14815 {
14816     int i, j, fromX, fromY, toX, toY;
14817     int whiteToPlay;
14818     char buf[128];
14819     char *p, *q;
14820     int emptycount;
14821     ChessSquare piece;
14822
14823     whiteToPlay = (gameMode == EditPosition) ?
14824       !blackPlaysFirst : (move % 2 == 0);
14825     p = buf;
14826
14827     /* Piece placement data */
14828     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14829         emptycount = 0;
14830         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14831             if (boards[move][i][j] == EmptySquare) {
14832                 emptycount++;
14833             } else { ChessSquare piece = boards[move][i][j];
14834                 if (emptycount > 0) {
14835                     if(emptycount<10) /* [HGM] can be >= 10 */
14836                         *p++ = '0' + emptycount;
14837                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14838                     emptycount = 0;
14839                 }
14840                 if(PieceToChar(piece) == '+') {
14841                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14842                     *p++ = '+';
14843                     piece = (ChessSquare)(DEMOTED piece);
14844                 }
14845                 *p++ = PieceToChar(piece);
14846                 if(p[-1] == '~') {
14847                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14848                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14849                     *p++ = '~';
14850                 }
14851             }
14852         }
14853         if (emptycount > 0) {
14854             if(emptycount<10) /* [HGM] can be >= 10 */
14855                 *p++ = '0' + emptycount;
14856             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14857             emptycount = 0;
14858         }
14859         *p++ = '/';
14860     }
14861     *(p - 1) = ' ';
14862
14863     /* [HGM] print Crazyhouse or Shogi holdings */
14864     if( gameInfo.holdingsWidth ) {
14865         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14866         q = p;
14867         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14868             piece = boards[move][i][BOARD_WIDTH-1];
14869             if( piece != EmptySquare )
14870               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14871                   *p++ = PieceToChar(piece);
14872         }
14873         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14874             piece = boards[move][BOARD_HEIGHT-i-1][0];
14875             if( piece != EmptySquare )
14876               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14877                   *p++ = PieceToChar(piece);
14878         }
14879
14880         if( q == p ) *p++ = '-';
14881         *p++ = ']';
14882         *p++ = ' ';
14883     }
14884
14885     /* Active color */
14886     *p++ = whiteToPlay ? 'w' : 'b';
14887     *p++ = ' ';
14888
14889   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14890     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14891   } else {
14892   if(nrCastlingRights) {
14893      q = p;
14894      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14895        /* [HGM] write directly from rights */
14896            if(boards[move][CASTLING][2] != NoRights &&
14897               boards[move][CASTLING][0] != NoRights   )
14898                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14899            if(boards[move][CASTLING][2] != NoRights &&
14900               boards[move][CASTLING][1] != NoRights   )
14901                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14902            if(boards[move][CASTLING][5] != NoRights &&
14903               boards[move][CASTLING][3] != NoRights   )
14904                 *p++ = boards[move][CASTLING][3] + AAA;
14905            if(boards[move][CASTLING][5] != NoRights &&
14906               boards[move][CASTLING][4] != NoRights   )
14907                 *p++ = boards[move][CASTLING][4] + AAA;
14908      } else {
14909
14910         /* [HGM] write true castling rights */
14911         if( nrCastlingRights == 6 ) {
14912             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14913                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14914             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14915                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14916             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14917                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14918             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14919                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14920         }
14921      }
14922      if (q == p) *p++ = '-'; /* No castling rights */
14923      *p++ = ' ';
14924   }
14925
14926   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14927      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14928     /* En passant target square */
14929     if (move > backwardMostMove) {
14930         fromX = moveList[move - 1][0] - AAA;
14931         fromY = moveList[move - 1][1] - ONE;
14932         toX = moveList[move - 1][2] - AAA;
14933         toY = moveList[move - 1][3] - ONE;
14934         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14935             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14936             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14937             fromX == toX) {
14938             /* 2-square pawn move just happened */
14939             *p++ = toX + AAA;
14940             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14941         } else {
14942             *p++ = '-';
14943         }
14944     } else if(move == backwardMostMove) {
14945         // [HGM] perhaps we should always do it like this, and forget the above?
14946         if((signed char)boards[move][EP_STATUS] >= 0) {
14947             *p++ = boards[move][EP_STATUS] + AAA;
14948             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14949         } else {
14950             *p++ = '-';
14951         }
14952     } else {
14953         *p++ = '-';
14954     }
14955     *p++ = ' ';
14956   }
14957   }
14958
14959     /* [HGM] find reversible plies */
14960     {   int i = 0, j=move;
14961
14962         if (appData.debugMode) { int k;
14963             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14964             for(k=backwardMostMove; k<=forwardMostMove; k++)
14965                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14966
14967         }
14968
14969         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14970         if( j == backwardMostMove ) i += initialRulePlies;
14971         sprintf(p, "%d ", i);
14972         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14973     }
14974     /* Fullmove number */
14975     sprintf(p, "%d", (move / 2) + 1);
14976
14977     return StrSave(buf);
14978 }
14979
14980 Boolean
14981 ParseFEN(board, blackPlaysFirst, fen)
14982     Board board;
14983      int *blackPlaysFirst;
14984      char *fen;
14985 {
14986     int i, j;
14987     char *p, c;
14988     int emptycount;
14989     ChessSquare piece;
14990
14991     p = fen;
14992
14993     /* [HGM] by default clear Crazyhouse holdings, if present */
14994     if(gameInfo.holdingsWidth) {
14995        for(i=0; i<BOARD_HEIGHT; i++) {
14996            board[i][0]             = EmptySquare; /* black holdings */
14997            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14998            board[i][1]             = (ChessSquare) 0; /* black counts */
14999            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15000        }
15001     }
15002
15003     /* Piece placement data */
15004     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15005         j = 0;
15006         for (;;) {
15007             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15008                 if (*p == '/') p++;
15009                 emptycount = gameInfo.boardWidth - j;
15010                 while (emptycount--)
15011                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15012                 break;
15013 #if(BOARD_FILES >= 10)
15014             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15015                 p++; emptycount=10;
15016                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15017                 while (emptycount--)
15018                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15019 #endif
15020             } else if (isdigit(*p)) {
15021                 emptycount = *p++ - '0';
15022                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15023                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15024                 while (emptycount--)
15025                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15026             } else if (*p == '+' || isalpha(*p)) {
15027                 if (j >= gameInfo.boardWidth) return FALSE;
15028                 if(*p=='+') {
15029                     piece = CharToPiece(*++p);
15030                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15031                     piece = (ChessSquare) (PROMOTED piece ); p++;
15032                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15033                 } else piece = CharToPiece(*p++);
15034
15035                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15036                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15037                     piece = (ChessSquare) (PROMOTED piece);
15038                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15039                     p++;
15040                 }
15041                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15042             } else {
15043                 return FALSE;
15044             }
15045         }
15046     }
15047     while (*p == '/' || *p == ' ') p++;
15048
15049     /* [HGM] look for Crazyhouse holdings here */
15050     while(*p==' ') p++;
15051     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15052         if(*p == '[') p++;
15053         if(*p == '-' ) p++; /* empty holdings */ else {
15054             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15055             /* if we would allow FEN reading to set board size, we would   */
15056             /* have to add holdings and shift the board read so far here   */
15057             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15058                 p++;
15059                 if((int) piece >= (int) BlackPawn ) {
15060                     i = (int)piece - (int)BlackPawn;
15061                     i = PieceToNumber((ChessSquare)i);
15062                     if( i >= gameInfo.holdingsSize ) return FALSE;
15063                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15064                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15065                 } else {
15066                     i = (int)piece - (int)WhitePawn;
15067                     i = PieceToNumber((ChessSquare)i);
15068                     if( i >= gameInfo.holdingsSize ) return FALSE;
15069                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15070                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15071                 }
15072             }
15073         }
15074         if(*p == ']') p++;
15075     }
15076
15077     while(*p == ' ') p++;
15078
15079     /* Active color */
15080     c = *p++;
15081     if(appData.colorNickNames) {
15082       if( c == appData.colorNickNames[0] ) c = 'w'; else
15083       if( c == appData.colorNickNames[1] ) c = 'b';
15084     }
15085     switch (c) {
15086       case 'w':
15087         *blackPlaysFirst = FALSE;
15088         break;
15089       case 'b':
15090         *blackPlaysFirst = TRUE;
15091         break;
15092       default:
15093         return FALSE;
15094     }
15095
15096     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15097     /* return the extra info in global variiables             */
15098
15099     /* set defaults in case FEN is incomplete */
15100     board[EP_STATUS] = EP_UNKNOWN;
15101     for(i=0; i<nrCastlingRights; i++ ) {
15102         board[CASTLING][i] =
15103             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15104     }   /* assume possible unless obviously impossible */
15105     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15106     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15107     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15108                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15109     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15110     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15111     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15112                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15113     FENrulePlies = 0;
15114
15115     while(*p==' ') p++;
15116     if(nrCastlingRights) {
15117       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15118           /* castling indicator present, so default becomes no castlings */
15119           for(i=0; i<nrCastlingRights; i++ ) {
15120                  board[CASTLING][i] = NoRights;
15121           }
15122       }
15123       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15124              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15125              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15126              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15127         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15128
15129         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15130             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15131             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15132         }
15133         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15134             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15135         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15136                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15137         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15138                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15139         switch(c) {
15140           case'K':
15141               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15142               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15143               board[CASTLING][2] = whiteKingFile;
15144               break;
15145           case'Q':
15146               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15147               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15148               board[CASTLING][2] = whiteKingFile;
15149               break;
15150           case'k':
15151               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15152               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15153               board[CASTLING][5] = blackKingFile;
15154               break;
15155           case'q':
15156               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15157               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15158               board[CASTLING][5] = blackKingFile;
15159           case '-':
15160               break;
15161           default: /* FRC castlings */
15162               if(c >= 'a') { /* black rights */
15163                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15164                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15165                   if(i == BOARD_RGHT) break;
15166                   board[CASTLING][5] = i;
15167                   c -= AAA;
15168                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15169                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15170                   if(c > i)
15171                       board[CASTLING][3] = c;
15172                   else
15173                       board[CASTLING][4] = c;
15174               } else { /* white rights */
15175                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15176                     if(board[0][i] == WhiteKing) break;
15177                   if(i == BOARD_RGHT) break;
15178                   board[CASTLING][2] = i;
15179                   c -= AAA - 'a' + 'A';
15180                   if(board[0][c] >= WhiteKing) break;
15181                   if(c > i)
15182                       board[CASTLING][0] = c;
15183                   else
15184                       board[CASTLING][1] = c;
15185               }
15186         }
15187       }
15188       for(i=0; i<nrCastlingRights; i++)
15189         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15190     if (appData.debugMode) {
15191         fprintf(debugFP, "FEN castling rights:");
15192         for(i=0; i<nrCastlingRights; i++)
15193         fprintf(debugFP, " %d", board[CASTLING][i]);
15194         fprintf(debugFP, "\n");
15195     }
15196
15197       while(*p==' ') p++;
15198     }
15199
15200     /* read e.p. field in games that know e.p. capture */
15201     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15202        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15203       if(*p=='-') {
15204         p++; board[EP_STATUS] = EP_NONE;
15205       } else {
15206          char c = *p++ - AAA;
15207
15208          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15209          if(*p >= '0' && *p <='9') p++;
15210          board[EP_STATUS] = c;
15211       }
15212     }
15213
15214
15215     if(sscanf(p, "%d", &i) == 1) {
15216         FENrulePlies = i; /* 50-move ply counter */
15217         /* (The move number is still ignored)    */
15218     }
15219
15220     return TRUE;
15221 }
15222
15223 void
15224 EditPositionPasteFEN(char *fen)
15225 {
15226   if (fen != NULL) {
15227     Board initial_position;
15228
15229     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15230       DisplayError(_("Bad FEN position in clipboard"), 0);
15231       return ;
15232     } else {
15233       int savedBlackPlaysFirst = blackPlaysFirst;
15234       EditPositionEvent();
15235       blackPlaysFirst = savedBlackPlaysFirst;
15236       CopyBoard(boards[0], initial_position);
15237       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15238       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15239       DisplayBothClocks();
15240       DrawPosition(FALSE, boards[currentMove]);
15241     }
15242   }
15243 }
15244
15245 static char cseq[12] = "\\   ";
15246
15247 Boolean set_cont_sequence(char *new_seq)
15248 {
15249     int len;
15250     Boolean ret;
15251
15252     // handle bad attempts to set the sequence
15253         if (!new_seq)
15254                 return 0; // acceptable error - no debug
15255
15256     len = strlen(new_seq);
15257     ret = (len > 0) && (len < sizeof(cseq));
15258     if (ret)
15259       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15260     else if (appData.debugMode)
15261       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15262     return ret;
15263 }
15264
15265 /*
15266     reformat a source message so words don't cross the width boundary.  internal
15267     newlines are not removed.  returns the wrapped size (no null character unless
15268     included in source message).  If dest is NULL, only calculate the size required
15269     for the dest buffer.  lp argument indicats line position upon entry, and it's
15270     passed back upon exit.
15271 */
15272 int wrap(char *dest, char *src, int count, int width, int *lp)
15273 {
15274     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15275
15276     cseq_len = strlen(cseq);
15277     old_line = line = *lp;
15278     ansi = len = clen = 0;
15279
15280     for (i=0; i < count; i++)
15281     {
15282         if (src[i] == '\033')
15283             ansi = 1;
15284
15285         // if we hit the width, back up
15286         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15287         {
15288             // store i & len in case the word is too long
15289             old_i = i, old_len = len;
15290
15291             // find the end of the last word
15292             while (i && src[i] != ' ' && src[i] != '\n')
15293             {
15294                 i--;
15295                 len--;
15296             }
15297
15298             // word too long?  restore i & len before splitting it
15299             if ((old_i-i+clen) >= width)
15300             {
15301                 i = old_i;
15302                 len = old_len;
15303             }
15304
15305             // extra space?
15306             if (i && src[i-1] == ' ')
15307                 len--;
15308
15309             if (src[i] != ' ' && src[i] != '\n')
15310             {
15311                 i--;
15312                 if (len)
15313                     len--;
15314             }
15315
15316             // now append the newline and continuation sequence
15317             if (dest)
15318                 dest[len] = '\n';
15319             len++;
15320             if (dest)
15321                 strncpy(dest+len, cseq, cseq_len);
15322             len += cseq_len;
15323             line = cseq_len;
15324             clen = cseq_len;
15325             continue;
15326         }
15327
15328         if (dest)
15329             dest[len] = src[i];
15330         len++;
15331         if (!ansi)
15332             line++;
15333         if (src[i] == '\n')
15334             line = 0;
15335         if (src[i] == 'm')
15336             ansi = 0;
15337     }
15338     if (dest && appData.debugMode)
15339     {
15340         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15341             count, width, line, len, *lp);
15342         show_bytes(debugFP, src, count);
15343         fprintf(debugFP, "\ndest: ");
15344         show_bytes(debugFP, dest, len);
15345         fprintf(debugFP, "\n");
15346     }
15347     *lp = dest ? line : old_line;
15348
15349     return len;
15350 }
15351
15352 // [HGM] vari: routines for shelving variations
15353
15354 void
15355 PushTail(int firstMove, int lastMove)
15356 {
15357         int i, j, nrMoves = lastMove - firstMove;
15358
15359         if(appData.icsActive) { // only in local mode
15360                 forwardMostMove = currentMove; // mimic old ICS behavior
15361                 return;
15362         }
15363         if(storedGames >= MAX_VARIATIONS-1) return;
15364
15365         // push current tail of game on stack
15366         savedResult[storedGames] = gameInfo.result;
15367         savedDetails[storedGames] = gameInfo.resultDetails;
15368         gameInfo.resultDetails = NULL;
15369         savedFirst[storedGames] = firstMove;
15370         savedLast [storedGames] = lastMove;
15371         savedFramePtr[storedGames] = framePtr;
15372         framePtr -= nrMoves; // reserve space for the boards
15373         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15374             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15375             for(j=0; j<MOVE_LEN; j++)
15376                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15377             for(j=0; j<2*MOVE_LEN; j++)
15378                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15379             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15380             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15381             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15382             pvInfoList[firstMove+i-1].depth = 0;
15383             commentList[framePtr+i] = commentList[firstMove+i];
15384             commentList[firstMove+i] = NULL;
15385         }
15386
15387         storedGames++;
15388         forwardMostMove = firstMove; // truncate game so we can start variation
15389         if(storedGames == 1) GreyRevert(FALSE);
15390 }
15391
15392 Boolean
15393 PopTail(Boolean annotate)
15394 {
15395         int i, j, nrMoves;
15396         char buf[8000], moveBuf[20];
15397
15398         if(appData.icsActive) return FALSE; // only in local mode
15399         if(!storedGames) return FALSE; // sanity
15400         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15401
15402         storedGames--;
15403         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15404         nrMoves = savedLast[storedGames] - currentMove;
15405         if(annotate) {
15406                 int cnt = 10;
15407                 if(!WhiteOnMove(currentMove))
15408                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15409                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15410                 for(i=currentMove; i<forwardMostMove; i++) {
15411                         if(WhiteOnMove(i))
15412                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15413                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15414                         strcat(buf, moveBuf);
15415                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15416                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15417                 }
15418                 strcat(buf, ")");
15419         }
15420         for(i=1; i<=nrMoves; i++) { // copy last variation back
15421             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15422             for(j=0; j<MOVE_LEN; j++)
15423                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15424             for(j=0; j<2*MOVE_LEN; j++)
15425                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15426             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15427             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15428             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15429             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15430             commentList[currentMove+i] = commentList[framePtr+i];
15431             commentList[framePtr+i] = NULL;
15432         }
15433         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15434         framePtr = savedFramePtr[storedGames];
15435         gameInfo.result = savedResult[storedGames];
15436         if(gameInfo.resultDetails != NULL) {
15437             free(gameInfo.resultDetails);
15438       }
15439         gameInfo.resultDetails = savedDetails[storedGames];
15440         forwardMostMove = currentMove + nrMoves;
15441         if(storedGames == 0) GreyRevert(TRUE);
15442         return TRUE;
15443 }
15444
15445 void
15446 CleanupTail()
15447 {       // remove all shelved variations
15448         int i;
15449         for(i=0; i<storedGames; i++) {
15450             if(savedDetails[i])
15451                 free(savedDetails[i]);
15452             savedDetails[i] = NULL;
15453         }
15454         for(i=framePtr; i<MAX_MOVES; i++) {
15455                 if(commentList[i]) free(commentList[i]);
15456                 commentList[i] = NULL;
15457         }
15458         framePtr = MAX_MOVES-1;
15459         storedGames = 0;
15460 }
15461
15462 void
15463 LoadVariation(int index, char *text)
15464 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15465         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15466         int level = 0, move;
15467
15468         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15469         // first find outermost bracketing variation
15470         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15471             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15472                 if(*p == '{') wait = '}'; else
15473                 if(*p == '[') wait = ']'; else
15474                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15475                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15476             }
15477             if(*p == wait) wait = NULLCHAR; // closing ]} found
15478             p++;
15479         }
15480         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15481         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15482         end[1] = NULLCHAR; // clip off comment beyond variation
15483         ToNrEvent(currentMove-1);
15484         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15485         // kludge: use ParsePV() to append variation to game
15486         move = currentMove;
15487         ParsePV(start, TRUE);
15488         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15489         ClearPremoveHighlights();
15490         CommentPopDown();
15491         ToNrEvent(currentMove+1);
15492 }
15493