Make sweep-select promotions work in WinBoard
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy( char *dst, const char *src, size_t count )
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble(u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags(index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386     flags &= ~F_ALL_CASTLE_OK;
387     break;
388   default:
389     break;
390   }
391   return flags;
392 }
393
394 FILE *gameFileFP, *debugFP;
395
396 /*
397     [AS] Note: sometimes, the sscanf() function is used to parse the input
398     into a fixed-size buffer. Because of this, we must be prepared to
399     receive strings as long as the size of the input buffer, which is currently
400     set to 4K for Windows and 8K for the rest.
401     So, we must either allocate sufficiently large buffers here, or
402     reduce the size of the input buffer in the input reading part.
403 */
404
405 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
406 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
407 char thinkOutput1[MSG_SIZ*10];
408
409 ChessProgramState first, second;
410
411 /* premove variables */
412 int premoveToX = 0;
413 int premoveToY = 0;
414 int premoveFromX = 0;
415 int premoveFromY = 0;
416 int premovePromoChar = 0;
417 int gotPremove = 0;
418 Boolean alarmSounded;
419 /* end premove variables */
420
421 char *ics_prefix = "$";
422 int ics_type = ICS_GENERIC;
423
424 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
425 int pauseExamForwardMostMove = 0;
426 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
427 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
428 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
429 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
430 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
431 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
432 int whiteFlag = FALSE, blackFlag = FALSE;
433 int userOfferedDraw = FALSE;
434 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
435 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
436 int cmailMoveType[CMAIL_MAX_GAMES];
437 long ics_clock_paused = 0;
438 ProcRef icsPR = NoProc, cmailPR = NoProc;
439 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
440 GameMode gameMode = BeginningOfGame;
441 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
442 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
443 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
444 int hiddenThinkOutputState = 0; /* [AS] */
445 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
446 int adjudicateLossPlies = 6;
447 char white_holding[64], black_holding[64];
448 TimeMark lastNodeCountTime;
449 long lastNodeCount=0;
450 int shiftKey; // [HGM] set by mouse handler
451
452 int have_sent_ICS_logon = 0;
453 int movesPerSession;
454 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
455 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
456 long timeControl_2; /* [AS] Allow separate time controls */
457 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
458 long timeRemaining[2][MAX_MOVES];
459 int matchGame = 0;
460 TimeMark programStartTime;
461 char ics_handle[MSG_SIZ];
462 int have_set_title = 0;
463
464 /* animateTraining preserves the state of appData.animate
465  * when Training mode is activated. This allows the
466  * response to be animated when appData.animate == TRUE and
467  * appData.animateDragging == TRUE.
468  */
469 Boolean animateTraining;
470
471 GameInfo gameInfo;
472
473 AppData appData;
474
475 Board boards[MAX_MOVES];
476 /* [HGM] Following 7 needed for accurate legality tests: */
477 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
478 signed char  initialRights[BOARD_FILES];
479 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
480 int   initialRulePlies, FENrulePlies;
481 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
482 int loadFlag = 0;
483 int shuffleOpenings;
484 int mute; // mute all sounds
485
486 // [HGM] vari: next 12 to save and restore variations
487 #define MAX_VARIATIONS 10
488 int framePtr = MAX_MOVES-1; // points to free stack entry
489 int storedGames = 0;
490 int savedFirst[MAX_VARIATIONS];
491 int savedLast[MAX_VARIATIONS];
492 int savedFramePtr[MAX_VARIATIONS];
493 char *savedDetails[MAX_VARIATIONS];
494 ChessMove savedResult[MAX_VARIATIONS];
495
496 void PushTail P((int firstMove, int lastMove));
497 Boolean PopTail P((Boolean annotate));
498 void CleanupTail P((void));
499
500 ChessSquare  FIDEArray[2][BOARD_FILES] = {
501     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
502         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
504         BlackKing, BlackBishop, BlackKnight, BlackRook }
505 };
506
507 ChessSquare twoKingsArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackKing, BlackKnight, BlackRook }
512 };
513
514 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
516         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
517     { BlackRook, BlackMan, BlackBishop, BlackQueen,
518         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
519 };
520
521 ChessSquare SpartanArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
524     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
525         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
526 };
527
528 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
532         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
533 };
534
535 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
536     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
537         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
538     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
539         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
540 };
541
542 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
544         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackMan, BlackFerz,
546         BlackKing, BlackMan, BlackKnight, BlackRook }
547 };
548
549
550 #if (BOARD_FILES>=10)
551 ChessSquare ShogiArray[2][BOARD_FILES] = {
552     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
553         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
554     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
555         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
556 };
557
558 ChessSquare XiangqiArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
560         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
562         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
563 };
564
565 ChessSquare CapablancaArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
569         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
570 };
571
572 ChessSquare GreatArray[2][BOARD_FILES] = {
573     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
574         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
575     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
576         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
577 };
578
579 ChessSquare JanusArray[2][BOARD_FILES] = {
580     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
581         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
582     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
583         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
584 };
585
586 #ifdef GOTHIC
587 ChessSquare GothicArray[2][BOARD_FILES] = {
588     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
589         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
590     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
591         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
592 };
593 #else // !GOTHIC
594 #define GothicArray CapablancaArray
595 #endif // !GOTHIC
596
597 #ifdef FALCON
598 ChessSquare FalconArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
600         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
602         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
603 };
604 #else // !FALCON
605 #define FalconArray CapablancaArray
606 #endif // !FALCON
607
608 #else // !(BOARD_FILES>=10)
609 #define XiangqiPosition FIDEArray
610 #define CapablancaArray FIDEArray
611 #define GothicArray FIDEArray
612 #define GreatArray FIDEArray
613 #endif // !(BOARD_FILES>=10)
614
615 #if (BOARD_FILES>=12)
616 ChessSquare CourierArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
618         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
620         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
621 };
622 #else // !(BOARD_FILES>=12)
623 #define CourierArray CapablancaArray
624 #endif // !(BOARD_FILES>=12)
625
626
627 Board initialPosition;
628
629
630 /* Convert str to a rating. Checks for special cases of "----",
631
632    "++++", etc. Also strips ()'s */
633 int
634 string_to_rating(str)
635   char *str;
636 {
637   while(*str && !isdigit(*str)) ++str;
638   if (!*str)
639     return 0;   /* One of the special "no rating" cases */
640   else
641     return atoi(str);
642 }
643
644 void
645 ClearProgramStats()
646 {
647     /* Init programStats */
648     programStats.movelist[0] = 0;
649     programStats.depth = 0;
650     programStats.nr_moves = 0;
651     programStats.moves_left = 0;
652     programStats.nodes = 0;
653     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
654     programStats.score = 0;
655     programStats.got_only_move = 0;
656     programStats.got_fail = 0;
657     programStats.line_is_book = 0;
658 }
659
660 void
661 InitBackEnd1()
662 {
663     int matched, min, sec;
664
665     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
666     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
667
668     GetTimeMark(&programStartTime);
669     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
670
671     ClearProgramStats();
672     programStats.ok_to_send = 1;
673     programStats.seen_stat = 0;
674
675     /*
676      * Initialize game list
677      */
678     ListNew(&gameList);
679
680
681     /*
682      * Internet chess server status
683      */
684     if (appData.icsActive) {
685         appData.matchMode = FALSE;
686         appData.matchGames = 0;
687 #if ZIPPY
688         appData.noChessProgram = !appData.zippyPlay;
689 #else
690         appData.zippyPlay = FALSE;
691         appData.zippyTalk = FALSE;
692         appData.noChessProgram = TRUE;
693 #endif
694         if (*appData.icsHelper != NULLCHAR) {
695             appData.useTelnet = TRUE;
696             appData.telnetProgram = appData.icsHelper;
697         }
698     } else {
699         appData.zippyTalk = appData.zippyPlay = FALSE;
700     }
701
702     /* [AS] Initialize pv info list [HGM] and game state */
703     {
704         int i, j;
705
706         for( i=0; i<=framePtr; i++ ) {
707             pvInfoList[i].depth = -1;
708             boards[i][EP_STATUS] = EP_NONE;
709             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
710         }
711     }
712
713     /*
714      * Parse timeControl resource
715      */
716     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
717                           appData.movesPerSession)) {
718         char buf[MSG_SIZ];
719         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
720         DisplayFatalError(buf, 0, 2);
721     }
722
723     /*
724      * Parse searchTime resource
725      */
726     if (*appData.searchTime != NULLCHAR) {
727         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
728         if (matched == 1) {
729             searchTime = min * 60;
730         } else if (matched == 2) {
731             searchTime = min * 60 + sec;
732         } else {
733             char buf[MSG_SIZ];
734             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
735             DisplayFatalError(buf, 0, 2);
736         }
737     }
738
739     /* [AS] Adjudication threshold */
740     adjudicateLossThreshold = appData.adjudicateLossThreshold;
741
742     first.which = "first";
743     second.which = "second";
744     first.maybeThinking = second.maybeThinking = FALSE;
745     first.pr = second.pr = NoProc;
746     first.isr = second.isr = NULL;
747     first.sendTime = second.sendTime = 2;
748     first.sendDrawOffers = 1;
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756     first.program = appData.firstChessProgram;
757     second.program = appData.secondChessProgram;
758     first.host = appData.firstHost;
759     second.host = appData.secondHost;
760     first.dir = appData.firstDirectory;
761     second.dir = appData.secondDirectory;
762     first.other = &second;
763     second.other = &first;
764     first.initString = appData.initString;
765     second.initString = appData.secondInitString;
766     first.computerString = appData.firstComputerString;
767     second.computerString = appData.secondComputerString;
768     first.useSigint = second.useSigint = TRUE;
769     first.useSigterm = second.useSigterm = TRUE;
770     first.reuse = appData.reuseFirst;
771     second.reuse = appData.reuseSecond;
772     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
773     second.nps = appData.secondNPS;
774     first.useSetboard = second.useSetboard = FALSE;
775     first.useSAN = second.useSAN = FALSE;
776     first.usePing = second.usePing = FALSE;
777     first.lastPing = second.lastPing = 0;
778     first.lastPong = second.lastPong = 0;
779     first.usePlayother = second.usePlayother = FALSE;
780     first.useColors = second.useColors = TRUE;
781     first.useUsermove = second.useUsermove = FALSE;
782     first.sendICS = second.sendICS = FALSE;
783     first.sendName = second.sendName = appData.icsActive;
784     first.sdKludge = second.sdKludge = FALSE;
785     first.stKludge = second.stKludge = FALSE;
786     TidyProgramName(first.program, first.host, first.tidy);
787     TidyProgramName(second.program, second.host, second.tidy);
788     first.matchWins = second.matchWins = 0;
789     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
790     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
791     first.analysisSupport = second.analysisSupport = 2; /* detect */
792     first.analyzing = second.analyzing = FALSE;
793     first.initDone = second.initDone = FALSE;
794
795     /* New features added by Tord: */
796     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
797     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
798     /* End of new features added by Tord. */
799     first.fenOverride  = appData.fenOverride1;
800     second.fenOverride = appData.fenOverride2;
801
802     /* [HGM] time odds: set factor for each machine */
803     first.timeOdds  = appData.firstTimeOdds;
804     second.timeOdds = appData.secondTimeOdds;
805     { float norm = 1;
806         if(appData.timeOddsMode) {
807             norm = first.timeOdds;
808             if(norm > second.timeOdds) norm = second.timeOdds;
809         }
810         first.timeOdds /= norm;
811         second.timeOdds /= norm;
812     }
813
814     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
815     first.accumulateTC = appData.firstAccumulateTC;
816     second.accumulateTC = appData.secondAccumulateTC;
817     first.maxNrOfSessions = second.maxNrOfSessions = 1;
818
819     /* [HGM] debug */
820     first.debug = second.debug = FALSE;
821     first.supportsNPS = second.supportsNPS = UNKNOWN;
822
823     /* [HGM] options */
824     first.optionSettings  = appData.firstOptions;
825     second.optionSettings = appData.secondOptions;
826
827     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
828     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
829     first.isUCI = appData.firstIsUCI; /* [AS] */
830     second.isUCI = appData.secondIsUCI; /* [AS] */
831     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
832     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
833
834     if (appData.firstProtocolVersion > PROTOVER
835         || appData.firstProtocolVersion < 1)
836       {
837         char buf[MSG_SIZ];
838         int len;
839
840         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
841                        appData.firstProtocolVersion);
842         if( (len > MSG_SIZ) && appData.debugMode )
843           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
844
845         DisplayFatalError(buf, 0, 2);
846       }
847     else
848       {
849         first.protocolVersion = appData.firstProtocolVersion;
850       }
851
852     if (appData.secondProtocolVersion > PROTOVER
853         || appData.secondProtocolVersion < 1)
854       {
855         char buf[MSG_SIZ];
856         int len;
857
858         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
859                        appData.secondProtocolVersion);
860         if( (len > MSG_SIZ) && appData.debugMode )
861           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
862
863         DisplayFatalError(buf, 0, 2);
864       }
865     else
866       {
867         second.protocolVersion = appData.secondProtocolVersion;
868       }
869
870     if (appData.icsActive) {
871         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
872 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
873     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
874         appData.clockMode = FALSE;
875         first.sendTime = second.sendTime = 0;
876     }
877
878 #if ZIPPY
879     /* Override some settings from environment variables, for backward
880        compatibility.  Unfortunately it's not feasible to have the env
881        vars just set defaults, at least in xboard.  Ugh.
882     */
883     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
884       ZippyInit();
885     }
886 #endif
887
888     if (appData.noChessProgram) {
889         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
890         sprintf(programVersion, "%s", PACKAGE_STRING);
891     } else {
892       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
893       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
894       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
895     }
896
897     if (!appData.icsActive) {
898       char buf[MSG_SIZ];
899       int len;
900
901       /* Check for variants that are supported only in ICS mode,
902          or not at all.  Some that are accepted here nevertheless
903          have bugs; see comments below.
904       */
905       VariantClass variant = StringToVariant(appData.variant);
906       switch (variant) {
907       case VariantBughouse:     /* need four players and two boards */
908       case VariantKriegspiel:   /* need to hide pieces and move details */
909         /* case VariantFischeRandom: (Fabien: moved below) */
910         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
911         if( (len > MSG_SIZ) && appData.debugMode )
912           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
913
914         DisplayFatalError(buf, 0, 2);
915         return;
916
917       case VariantUnknown:
918       case VariantLoadable:
919       case Variant29:
920       case Variant30:
921       case Variant31:
922       case Variant32:
923       case Variant33:
924       case Variant34:
925       case Variant35:
926       case Variant36:
927       default:
928         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
929         if( (len > MSG_SIZ) && appData.debugMode )
930           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
931
932         DisplayFatalError(buf, 0, 2);
933         return;
934
935       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
936       case VariantFairy:      /* [HGM] TestLegality definitely off! */
937       case VariantGothic:     /* [HGM] should work */
938       case VariantCapablanca: /* [HGM] should work */
939       case VariantCourier:    /* [HGM] initial forced moves not implemented */
940       case VariantShogi:      /* [HGM] could still mate with pawn drop */
941       case VariantKnightmate: /* [HGM] should work */
942       case VariantCylinder:   /* [HGM] untested */
943       case VariantFalcon:     /* [HGM] untested */
944       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
945                                  offboard interposition not understood */
946       case VariantNormal:     /* definitely works! */
947       case VariantWildCastle: /* pieces not automatically shuffled */
948       case VariantNoCastle:   /* pieces not automatically shuffled */
949       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
950       case VariantLosers:     /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantSuicide:    /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantGiveaway:   /* should work except for win condition,
955                                  and doesn't know captures are mandatory */
956       case VariantTwoKings:   /* should work */
957       case VariantAtomic:     /* should work except for win condition */
958       case Variant3Check:     /* should work except for win condition */
959       case VariantShatranj:   /* should work except for all win conditions */
960       case VariantMakruk:     /* should work except for daw countdown */
961       case VariantBerolina:   /* might work if TestLegality is off */
962       case VariantCapaRandom: /* should work */
963       case VariantJanus:      /* should work */
964       case VariantSuper:      /* experimental */
965       case VariantGreat:      /* experimental, requires legality testing to be off */
966       case VariantSChess:     /* S-Chess, should work */
967       case VariantSpartan:    /* should work */
968         break;
969       }
970     }
971
972     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
973     InitEngineUCI( installDir, &second );
974 }
975
976 int NextIntegerFromString( char ** str, long * value )
977 {
978     int result = -1;
979     char * s = *str;
980
981     while( *s == ' ' || *s == '\t' ) {
982         s++;
983     }
984
985     *value = 0;
986
987     if( *s >= '0' && *s <= '9' ) {
988         while( *s >= '0' && *s <= '9' ) {
989             *value = *value * 10 + (*s - '0');
990             s++;
991         }
992
993         result = 0;
994     }
995
996     *str = s;
997
998     return result;
999 }
1000
1001 int NextTimeControlFromString( char ** str, long * value )
1002 {
1003     long temp;
1004     int result = NextIntegerFromString( str, &temp );
1005
1006     if( result == 0 ) {
1007         *value = temp * 60; /* Minutes */
1008         if( **str == ':' ) {
1009             (*str)++;
1010             result = NextIntegerFromString( str, &temp );
1011             *value += temp; /* Seconds */
1012         }
1013     }
1014
1015     return result;
1016 }
1017
1018 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1019 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1020     int result = -1, type = 0; long temp, temp2;
1021
1022     if(**str != ':') return -1; // old params remain in force!
1023     (*str)++;
1024     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1025     if( NextIntegerFromString( str, &temp ) ) return -1;
1026     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1027
1028     if(**str != '/') {
1029         /* time only: incremental or sudden-death time control */
1030         if(**str == '+') { /* increment follows; read it */
1031             (*str)++;
1032             if(**str == '!') type = *(*str)++; // Bronstein TC
1033             if(result = NextIntegerFromString( str, &temp2)) return -1;
1034             *inc = temp2 * 1000;
1035             if(**str == '.') { // read fraction of increment
1036                 char *start = ++(*str);
1037                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1038                 temp2 *= 1000;
1039                 while(start++ < *str) temp2 /= 10;
1040                 *inc += temp2;
1041             }
1042         } else *inc = 0;
1043         *moves = 0; *tc = temp * 1000; *incType = type;
1044         return 0;
1045     }
1046
1047     (*str)++; /* classical time control */
1048     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1049
1050     if(result == 0) {
1051         *moves = temp;
1052         *tc    = temp2 * 1000;
1053         *inc   = 0;
1054         *incType = type;
1055     }
1056     return result;
1057 }
1058
1059 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1060 {   /* [HGM] get time to add from the multi-session time-control string */
1061     int incType, moves=1; /* kludge to force reading of first session */
1062     long time, increment;
1063     char *s = tcString;
1064
1065     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1066     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1067     do {
1068         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1069         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1070         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1071         if(movenr == -1) return time;    /* last move before new session     */
1072         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1073         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1074         if(!moves) return increment;     /* current session is incremental   */
1075         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1076     } while(movenr >= -1);               /* try again for next session       */
1077
1078     return 0; // no new time quota on this move
1079 }
1080
1081 int
1082 ParseTimeControl(tc, ti, mps)
1083      char *tc;
1084      float ti;
1085      int mps;
1086 {
1087   long tc1;
1088   long tc2;
1089   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1090   int min, sec=0;
1091
1092   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1093   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1094       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1095   if(ti > 0) {
1096
1097     if(mps)
1098       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1099     else 
1100       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1101   } else {
1102     if(mps)
1103       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1104     else 
1105       snprintf(buf, MSG_SIZ, ":%s", mytc);
1106   }
1107   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1108   
1109   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1110     return FALSE;
1111   }
1112
1113   if( *tc == '/' ) {
1114     /* Parse second time control */
1115     tc++;
1116
1117     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1118       return FALSE;
1119     }
1120
1121     if( tc2 == 0 ) {
1122       return FALSE;
1123     }
1124
1125     timeControl_2 = tc2 * 1000;
1126   }
1127   else {
1128     timeControl_2 = 0;
1129   }
1130
1131   if( tc1 == 0 ) {
1132     return FALSE;
1133   }
1134
1135   timeControl = tc1 * 1000;
1136
1137   if (ti >= 0) {
1138     timeIncrement = ti * 1000;  /* convert to ms */
1139     movesPerSession = 0;
1140   } else {
1141     timeIncrement = 0;
1142     movesPerSession = mps;
1143   }
1144   return TRUE;
1145 }
1146
1147 void
1148 InitBackEnd2()
1149 {
1150     if (appData.debugMode) {
1151         fprintf(debugFP, "%s\n", programVersion);
1152     }
1153
1154     set_cont_sequence(appData.wrapContSeq);
1155     if (appData.matchGames > 0) {
1156         appData.matchMode = TRUE;
1157     } else if (appData.matchMode) {
1158         appData.matchGames = 1;
1159     }
1160     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1161         appData.matchGames = appData.sameColorGames;
1162     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1163         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1164         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1165     }
1166     Reset(TRUE, FALSE);
1167     if (appData.noChessProgram || first.protocolVersion == 1) {
1168       InitBackEnd3();
1169     } else {
1170       /* kludge: allow timeout for initial "feature" commands */
1171       FreezeUI();
1172       DisplayMessage("", _("Starting chess program"));
1173       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1174     }
1175 }
1176
1177 void
1178 InitBackEnd3 P((void))
1179 {
1180     GameMode initialMode;
1181     char buf[MSG_SIZ];
1182     int err, len;
1183
1184     InitChessProgram(&first, startedFromSetupPosition);
1185
1186     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1187         free(programVersion);
1188         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1189         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1190     }
1191
1192     if (appData.icsActive) {
1193 #ifdef WIN32
1194         /* [DM] Make a console window if needed [HGM] merged ifs */
1195         ConsoleCreate();
1196 #endif
1197         err = establish();
1198         if (err != 0)
1199           {
1200             if (*appData.icsCommPort != NULLCHAR)
1201               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1202                              appData.icsCommPort);
1203             else
1204               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1205                         appData.icsHost, appData.icsPort);
1206
1207             if( (len > MSG_SIZ) && appData.debugMode )
1208               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1209
1210             DisplayFatalError(buf, err, 1);
1211             return;
1212         }
1213         SetICSMode();
1214         telnetISR =
1215           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1216         fromUserISR =
1217           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1218         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1219             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1220     } else if (appData.noChessProgram) {
1221         SetNCPMode();
1222     } else {
1223         SetGNUMode();
1224     }
1225
1226     if (*appData.cmailGameName != NULLCHAR) {
1227         SetCmailMode();
1228         OpenLoopback(&cmailPR);
1229         cmailISR =
1230           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1231     }
1232
1233     ThawUI();
1234     DisplayMessage("", "");
1235     if (StrCaseCmp(appData.initialMode, "") == 0) {
1236       initialMode = BeginningOfGame;
1237     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1238       initialMode = TwoMachinesPlay;
1239     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1240       initialMode = AnalyzeFile;
1241     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1242       initialMode = AnalyzeMode;
1243     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1244       initialMode = MachinePlaysWhite;
1245     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1246       initialMode = MachinePlaysBlack;
1247     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1248       initialMode = EditGame;
1249     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1250       initialMode = EditPosition;
1251     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1252       initialMode = Training;
1253     } else {
1254       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1255       if( (len > MSG_SIZ) && appData.debugMode )
1256         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1257
1258       DisplayFatalError(buf, 0, 2);
1259       return;
1260     }
1261
1262     if (appData.matchMode) {
1263         /* Set up machine vs. machine match */
1264         if (appData.noChessProgram) {
1265             DisplayFatalError(_("Can't have a match with no chess programs"),
1266                               0, 2);
1267             return;
1268         }
1269         matchMode = TRUE;
1270         matchGame = 1;
1271         if (*appData.loadGameFile != NULLCHAR) {
1272             int index = appData.loadGameIndex; // [HGM] autoinc
1273             if(index<0) lastIndex = index = 1;
1274             if (!LoadGameFromFile(appData.loadGameFile,
1275                                   index,
1276                                   appData.loadGameFile, FALSE)) {
1277                 DisplayFatalError(_("Bad game file"), 0, 1);
1278                 return;
1279             }
1280         } else if (*appData.loadPositionFile != NULLCHAR) {
1281             int index = appData.loadPositionIndex; // [HGM] autoinc
1282             if(index<0) lastIndex = index = 1;
1283             if (!LoadPositionFromFile(appData.loadPositionFile,
1284                                       index,
1285                                       appData.loadPositionFile)) {
1286                 DisplayFatalError(_("Bad position file"), 0, 1);
1287                 return;
1288             }
1289         }
1290         TwoMachinesEvent();
1291     } else if (*appData.cmailGameName != NULLCHAR) {
1292         /* Set up cmail mode */
1293         ReloadCmailMsgEvent(TRUE);
1294     } else {
1295         /* Set up other modes */
1296         if (initialMode == AnalyzeFile) {
1297           if (*appData.loadGameFile == NULLCHAR) {
1298             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1299             return;
1300           }
1301         }
1302         if (*appData.loadGameFile != NULLCHAR) {
1303             (void) LoadGameFromFile(appData.loadGameFile,
1304                                     appData.loadGameIndex,
1305                                     appData.loadGameFile, TRUE);
1306         } else if (*appData.loadPositionFile != NULLCHAR) {
1307             (void) LoadPositionFromFile(appData.loadPositionFile,
1308                                         appData.loadPositionIndex,
1309                                         appData.loadPositionFile);
1310             /* [HGM] try to make self-starting even after FEN load */
1311             /* to allow automatic setup of fairy variants with wtm */
1312             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1313                 gameMode = BeginningOfGame;
1314                 setboardSpoiledMachineBlack = 1;
1315             }
1316             /* [HGM] loadPos: make that every new game uses the setup */
1317             /* from file as long as we do not switch variant          */
1318             if(!blackPlaysFirst) {
1319                 startedFromPositionFile = TRUE;
1320                 CopyBoard(filePosition, boards[0]);
1321             }
1322         }
1323         if (initialMode == AnalyzeMode) {
1324           if (appData.noChessProgram) {
1325             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1326             return;
1327           }
1328           if (appData.icsActive) {
1329             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1330             return;
1331           }
1332           AnalyzeModeEvent();
1333         } else if (initialMode == AnalyzeFile) {
1334           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1335           ShowThinkingEvent();
1336           AnalyzeFileEvent();
1337           AnalysisPeriodicEvent(1);
1338         } else if (initialMode == MachinePlaysWhite) {
1339           if (appData.noChessProgram) {
1340             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1341                               0, 2);
1342             return;
1343           }
1344           if (appData.icsActive) {
1345             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1346                               0, 2);
1347             return;
1348           }
1349           MachineWhiteEvent();
1350         } else if (initialMode == MachinePlaysBlack) {
1351           if (appData.noChessProgram) {
1352             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1353                               0, 2);
1354             return;
1355           }
1356           if (appData.icsActive) {
1357             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1358                               0, 2);
1359             return;
1360           }
1361           MachineBlackEvent();
1362         } else if (initialMode == TwoMachinesPlay) {
1363           if (appData.noChessProgram) {
1364             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1365                               0, 2);
1366             return;
1367           }
1368           if (appData.icsActive) {
1369             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1370                               0, 2);
1371             return;
1372           }
1373           TwoMachinesEvent();
1374         } else if (initialMode == EditGame) {
1375           EditGameEvent();
1376         } else if (initialMode == EditPosition) {
1377           EditPositionEvent();
1378         } else if (initialMode == Training) {
1379           if (*appData.loadGameFile == NULLCHAR) {
1380             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1381             return;
1382           }
1383           TrainingEvent();
1384         }
1385     }
1386 }
1387
1388 /*
1389  * Establish will establish a contact to a remote host.port.
1390  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1391  *  used to talk to the host.
1392  * Returns 0 if okay, error code if not.
1393  */
1394 int
1395 establish()
1396 {
1397     char buf[MSG_SIZ];
1398
1399     if (*appData.icsCommPort != NULLCHAR) {
1400         /* Talk to the host through a serial comm port */
1401         return OpenCommPort(appData.icsCommPort, &icsPR);
1402
1403     } else if (*appData.gateway != NULLCHAR) {
1404         if (*appData.remoteShell == NULLCHAR) {
1405             /* Use the rcmd protocol to run telnet program on a gateway host */
1406             snprintf(buf, sizeof(buf), "%s %s %s",
1407                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1408             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1409
1410         } else {
1411             /* Use the rsh program to run telnet program on a gateway host */
1412             if (*appData.remoteUser == NULLCHAR) {
1413                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1414                         appData.gateway, appData.telnetProgram,
1415                         appData.icsHost, appData.icsPort);
1416             } else {
1417                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1418                         appData.remoteShell, appData.gateway,
1419                         appData.remoteUser, appData.telnetProgram,
1420                         appData.icsHost, appData.icsPort);
1421             }
1422             return StartChildProcess(buf, "", &icsPR);
1423
1424         }
1425     } else if (appData.useTelnet) {
1426         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1427
1428     } else {
1429         /* TCP socket interface differs somewhat between
1430            Unix and NT; handle details in the front end.
1431            */
1432         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1433     }
1434 }
1435
1436 void EscapeExpand(char *p, char *q)
1437 {       // [HGM] initstring: routine to shape up string arguments
1438         while(*p++ = *q++) if(p[-1] == '\\')
1439             switch(*q++) {
1440                 case 'n': p[-1] = '\n'; break;
1441                 case 'r': p[-1] = '\r'; break;
1442                 case 't': p[-1] = '\t'; break;
1443                 case '\\': p[-1] = '\\'; break;
1444                 case 0: *p = 0; return;
1445                 default: p[-1] = q[-1]; break;
1446             }
1447 }
1448
1449 void
1450 show_bytes(fp, buf, count)
1451      FILE *fp;
1452      char *buf;
1453      int count;
1454 {
1455     while (count--) {
1456         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1457             fprintf(fp, "\\%03o", *buf & 0xff);
1458         } else {
1459             putc(*buf, fp);
1460         }
1461         buf++;
1462     }
1463     fflush(fp);
1464 }
1465
1466 /* Returns an errno value */
1467 int
1468 OutputMaybeTelnet(pr, message, count, outError)
1469      ProcRef pr;
1470      char *message;
1471      int count;
1472      int *outError;
1473 {
1474     char buf[8192], *p, *q, *buflim;
1475     int left, newcount, outcount;
1476
1477     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1478         *appData.gateway != NULLCHAR) {
1479         if (appData.debugMode) {
1480             fprintf(debugFP, ">ICS: ");
1481             show_bytes(debugFP, message, count);
1482             fprintf(debugFP, "\n");
1483         }
1484         return OutputToProcess(pr, message, count, outError);
1485     }
1486
1487     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1488     p = message;
1489     q = buf;
1490     left = count;
1491     newcount = 0;
1492     while (left) {
1493         if (q >= buflim) {
1494             if (appData.debugMode) {
1495                 fprintf(debugFP, ">ICS: ");
1496                 show_bytes(debugFP, buf, newcount);
1497                 fprintf(debugFP, "\n");
1498             }
1499             outcount = OutputToProcess(pr, buf, newcount, outError);
1500             if (outcount < newcount) return -1; /* to be sure */
1501             q = buf;
1502             newcount = 0;
1503         }
1504         if (*p == '\n') {
1505             *q++ = '\r';
1506             newcount++;
1507         } else if (((unsigned char) *p) == TN_IAC) {
1508             *q++ = (char) TN_IAC;
1509             newcount ++;
1510         }
1511         *q++ = *p++;
1512         newcount++;
1513         left--;
1514     }
1515     if (appData.debugMode) {
1516         fprintf(debugFP, ">ICS: ");
1517         show_bytes(debugFP, buf, newcount);
1518         fprintf(debugFP, "\n");
1519     }
1520     outcount = OutputToProcess(pr, buf, newcount, outError);
1521     if (outcount < newcount) return -1; /* to be sure */
1522     return count;
1523 }
1524
1525 void
1526 read_from_player(isr, closure, message, count, error)
1527      InputSourceRef isr;
1528      VOIDSTAR closure;
1529      char *message;
1530      int count;
1531      int error;
1532 {
1533     int outError, outCount;
1534     static int gotEof = 0;
1535
1536     /* Pass data read from player on to ICS */
1537     if (count > 0) {
1538         gotEof = 0;
1539         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1540         if (outCount < count) {
1541             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1542         }
1543     } else if (count < 0) {
1544         RemoveInputSource(isr);
1545         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1546     } else if (gotEof++ > 0) {
1547         RemoveInputSource(isr);
1548         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1549     }
1550 }
1551
1552 void
1553 KeepAlive()
1554 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1555     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1556     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1557     SendToICS("date\n");
1558     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1559 }
1560
1561 /* added routine for printf style output to ics */
1562 void ics_printf(char *format, ...)
1563 {
1564     char buffer[MSG_SIZ];
1565     va_list args;
1566
1567     va_start(args, format);
1568     vsnprintf(buffer, sizeof(buffer), format, args);
1569     buffer[sizeof(buffer)-1] = '\0';
1570     SendToICS(buffer);
1571     va_end(args);
1572 }
1573
1574 void
1575 SendToICS(s)
1576      char *s;
1577 {
1578     int count, outCount, outError;
1579
1580     if (icsPR == NULL) return;
1581
1582     count = strlen(s);
1583     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1584     if (outCount < count) {
1585         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1586     }
1587 }
1588
1589 /* This is used for sending logon scripts to the ICS. Sending
1590    without a delay causes problems when using timestamp on ICC
1591    (at least on my machine). */
1592 void
1593 SendToICSDelayed(s,msdelay)
1594      char *s;
1595      long msdelay;
1596 {
1597     int count, outCount, outError;
1598
1599     if (icsPR == NULL) return;
1600
1601     count = strlen(s);
1602     if (appData.debugMode) {
1603         fprintf(debugFP, ">ICS: ");
1604         show_bytes(debugFP, s, count);
1605         fprintf(debugFP, "\n");
1606     }
1607     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1608                                       msdelay);
1609     if (outCount < count) {
1610         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1611     }
1612 }
1613
1614
1615 /* Remove all highlighting escape sequences in s
1616    Also deletes any suffix starting with '('
1617    */
1618 char *
1619 StripHighlightAndTitle(s)
1620      char *s;
1621 {
1622     static char retbuf[MSG_SIZ];
1623     char *p = retbuf;
1624
1625     while (*s != NULLCHAR) {
1626         while (*s == '\033') {
1627             while (*s != NULLCHAR && !isalpha(*s)) s++;
1628             if (*s != NULLCHAR) s++;
1629         }
1630         while (*s != NULLCHAR && *s != '\033') {
1631             if (*s == '(' || *s == '[') {
1632                 *p = NULLCHAR;
1633                 return retbuf;
1634             }
1635             *p++ = *s++;
1636         }
1637     }
1638     *p = NULLCHAR;
1639     return retbuf;
1640 }
1641
1642 /* Remove all highlighting escape sequences in s */
1643 char *
1644 StripHighlight(s)
1645      char *s;
1646 {
1647     static char retbuf[MSG_SIZ];
1648     char *p = retbuf;
1649
1650     while (*s != NULLCHAR) {
1651         while (*s == '\033') {
1652             while (*s != NULLCHAR && !isalpha(*s)) s++;
1653             if (*s != NULLCHAR) s++;
1654         }
1655         while (*s != NULLCHAR && *s != '\033') {
1656             *p++ = *s++;
1657         }
1658     }
1659     *p = NULLCHAR;
1660     return retbuf;
1661 }
1662
1663 char *variantNames[] = VARIANT_NAMES;
1664 char *
1665 VariantName(v)
1666      VariantClass v;
1667 {
1668     return variantNames[v];
1669 }
1670
1671
1672 /* Identify a variant from the strings the chess servers use or the
1673    PGN Variant tag names we use. */
1674 VariantClass
1675 StringToVariant(e)
1676      char *e;
1677 {
1678     char *p;
1679     int wnum = -1;
1680     VariantClass v = VariantNormal;
1681     int i, found = FALSE;
1682     char buf[MSG_SIZ];
1683     int len;
1684
1685     if (!e) return v;
1686
1687     /* [HGM] skip over optional board-size prefixes */
1688     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1689         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1690         while( *e++ != '_');
1691     }
1692
1693     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1694         v = VariantNormal;
1695         found = TRUE;
1696     } else
1697     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1698       if (StrCaseStr(e, variantNames[i])) {
1699         v = (VariantClass) i;
1700         found = TRUE;
1701         break;
1702       }
1703     }
1704
1705     if (!found) {
1706       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1707           || StrCaseStr(e, "wild/fr")
1708           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1709         v = VariantFischeRandom;
1710       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1711                  (i = 1, p = StrCaseStr(e, "w"))) {
1712         p += i;
1713         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1714         if (isdigit(*p)) {
1715           wnum = atoi(p);
1716         } else {
1717           wnum = -1;
1718         }
1719         switch (wnum) {
1720         case 0: /* FICS only, actually */
1721         case 1:
1722           /* Castling legal even if K starts on d-file */
1723           v = VariantWildCastle;
1724           break;
1725         case 2:
1726         case 3:
1727         case 4:
1728           /* Castling illegal even if K & R happen to start in
1729              normal positions. */
1730           v = VariantNoCastle;
1731           break;
1732         case 5:
1733         case 7:
1734         case 8:
1735         case 10:
1736         case 11:
1737         case 12:
1738         case 13:
1739         case 14:
1740         case 15:
1741         case 18:
1742         case 19:
1743           /* Castling legal iff K & R start in normal positions */
1744           v = VariantNormal;
1745           break;
1746         case 6:
1747         case 20:
1748         case 21:
1749           /* Special wilds for position setup; unclear what to do here */
1750           v = VariantLoadable;
1751           break;
1752         case 9:
1753           /* Bizarre ICC game */
1754           v = VariantTwoKings;
1755           break;
1756         case 16:
1757           v = VariantKriegspiel;
1758           break;
1759         case 17:
1760           v = VariantLosers;
1761           break;
1762         case 22:
1763           v = VariantFischeRandom;
1764           break;
1765         case 23:
1766           v = VariantCrazyhouse;
1767           break;
1768         case 24:
1769           v = VariantBughouse;
1770           break;
1771         case 25:
1772           v = Variant3Check;
1773           break;
1774         case 26:
1775           /* Not quite the same as FICS suicide! */
1776           v = VariantGiveaway;
1777           break;
1778         case 27:
1779           v = VariantAtomic;
1780           break;
1781         case 28:
1782           v = VariantShatranj;
1783           break;
1784
1785         /* Temporary names for future ICC types.  The name *will* change in
1786            the next xboard/WinBoard release after ICC defines it. */
1787         case 29:
1788           v = Variant29;
1789           break;
1790         case 30:
1791           v = Variant30;
1792           break;
1793         case 31:
1794           v = Variant31;
1795           break;
1796         case 32:
1797           v = Variant32;
1798           break;
1799         case 33:
1800           v = Variant33;
1801           break;
1802         case 34:
1803           v = Variant34;
1804           break;
1805         case 35:
1806           v = Variant35;
1807           break;
1808         case 36:
1809           v = Variant36;
1810           break;
1811         case 37:
1812           v = VariantShogi;
1813           break;
1814         case 38:
1815           v = VariantXiangqi;
1816           break;
1817         case 39:
1818           v = VariantCourier;
1819           break;
1820         case 40:
1821           v = VariantGothic;
1822           break;
1823         case 41:
1824           v = VariantCapablanca;
1825           break;
1826         case 42:
1827           v = VariantKnightmate;
1828           break;
1829         case 43:
1830           v = VariantFairy;
1831           break;
1832         case 44:
1833           v = VariantCylinder;
1834           break;
1835         case 45:
1836           v = VariantFalcon;
1837           break;
1838         case 46:
1839           v = VariantCapaRandom;
1840           break;
1841         case 47:
1842           v = VariantBerolina;
1843           break;
1844         case 48:
1845           v = VariantJanus;
1846           break;
1847         case 49:
1848           v = VariantSuper;
1849           break;
1850         case 50:
1851           v = VariantGreat;
1852           break;
1853         case -1:
1854           /* Found "wild" or "w" in the string but no number;
1855              must assume it's normal chess. */
1856           v = VariantNormal;
1857           break;
1858         default:
1859           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1860           if( (len > MSG_SIZ) && appData.debugMode )
1861             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1862
1863           DisplayError(buf, 0);
1864           v = VariantUnknown;
1865           break;
1866         }
1867       }
1868     }
1869     if (appData.debugMode) {
1870       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1871               e, wnum, VariantName(v));
1872     }
1873     return v;
1874 }
1875
1876 static int leftover_start = 0, leftover_len = 0;
1877 char star_match[STAR_MATCH_N][MSG_SIZ];
1878
1879 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1880    advance *index beyond it, and set leftover_start to the new value of
1881    *index; else return FALSE.  If pattern contains the character '*', it
1882    matches any sequence of characters not containing '\r', '\n', or the
1883    character following the '*' (if any), and the matched sequence(s) are
1884    copied into star_match.
1885    */
1886 int
1887 looking_at(buf, index, pattern)
1888      char *buf;
1889      int *index;
1890      char *pattern;
1891 {
1892     char *bufp = &buf[*index], *patternp = pattern;
1893     int star_count = 0;
1894     char *matchp = star_match[0];
1895
1896     for (;;) {
1897         if (*patternp == NULLCHAR) {
1898             *index = leftover_start = bufp - buf;
1899             *matchp = NULLCHAR;
1900             return TRUE;
1901         }
1902         if (*bufp == NULLCHAR) return FALSE;
1903         if (*patternp == '*') {
1904             if (*bufp == *(patternp + 1)) {
1905                 *matchp = NULLCHAR;
1906                 matchp = star_match[++star_count];
1907                 patternp += 2;
1908                 bufp++;
1909                 continue;
1910             } else if (*bufp == '\n' || *bufp == '\r') {
1911                 patternp++;
1912                 if (*patternp == NULLCHAR)
1913                   continue;
1914                 else
1915                   return FALSE;
1916             } else {
1917                 *matchp++ = *bufp++;
1918                 continue;
1919             }
1920         }
1921         if (*patternp != *bufp) return FALSE;
1922         patternp++;
1923         bufp++;
1924     }
1925 }
1926
1927 void
1928 SendToPlayer(data, length)
1929      char *data;
1930      int length;
1931 {
1932     int error, outCount;
1933     outCount = OutputToProcess(NoProc, data, length, &error);
1934     if (outCount < length) {
1935         DisplayFatalError(_("Error writing to display"), error, 1);
1936     }
1937 }
1938
1939 void
1940 PackHolding(packed, holding)
1941      char packed[];
1942      char *holding;
1943 {
1944     char *p = holding;
1945     char *q = packed;
1946     int runlength = 0;
1947     int curr = 9999;
1948     do {
1949         if (*p == curr) {
1950             runlength++;
1951         } else {
1952             switch (runlength) {
1953               case 0:
1954                 break;
1955               case 1:
1956                 *q++ = curr;
1957                 break;
1958               case 2:
1959                 *q++ = curr;
1960                 *q++ = curr;
1961                 break;
1962               default:
1963                 sprintf(q, "%d", runlength);
1964                 while (*q) q++;
1965                 *q++ = curr;
1966                 break;
1967             }
1968             runlength = 1;
1969             curr = *p;
1970         }
1971     } while (*p++);
1972     *q = NULLCHAR;
1973 }
1974
1975 /* Telnet protocol requests from the front end */
1976 void
1977 TelnetRequest(ddww, option)
1978      unsigned char ddww, option;
1979 {
1980     unsigned char msg[3];
1981     int outCount, outError;
1982
1983     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1984
1985     if (appData.debugMode) {
1986         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1987         switch (ddww) {
1988           case TN_DO:
1989             ddwwStr = "DO";
1990             break;
1991           case TN_DONT:
1992             ddwwStr = "DONT";
1993             break;
1994           case TN_WILL:
1995             ddwwStr = "WILL";
1996             break;
1997           case TN_WONT:
1998             ddwwStr = "WONT";
1999             break;
2000           default:
2001             ddwwStr = buf1;
2002             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2003             break;
2004         }
2005         switch (option) {
2006           case TN_ECHO:
2007             optionStr = "ECHO";
2008             break;
2009           default:
2010             optionStr = buf2;
2011             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2012             break;
2013         }
2014         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2015     }
2016     msg[0] = TN_IAC;
2017     msg[1] = ddww;
2018     msg[2] = option;
2019     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2020     if (outCount < 3) {
2021         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2022     }
2023 }
2024
2025 void
2026 DoEcho()
2027 {
2028     if (!appData.icsActive) return;
2029     TelnetRequest(TN_DO, TN_ECHO);
2030 }
2031
2032 void
2033 DontEcho()
2034 {
2035     if (!appData.icsActive) return;
2036     TelnetRequest(TN_DONT, TN_ECHO);
2037 }
2038
2039 void
2040 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2041 {
2042     /* put the holdings sent to us by the server on the board holdings area */
2043     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2044     char p;
2045     ChessSquare piece;
2046
2047     if(gameInfo.holdingsWidth < 2)  return;
2048     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2049         return; // prevent overwriting by pre-board holdings
2050
2051     if( (int)lowestPiece >= BlackPawn ) {
2052         holdingsColumn = 0;
2053         countsColumn = 1;
2054         holdingsStartRow = BOARD_HEIGHT-1;
2055         direction = -1;
2056     } else {
2057         holdingsColumn = BOARD_WIDTH-1;
2058         countsColumn = BOARD_WIDTH-2;
2059         holdingsStartRow = 0;
2060         direction = 1;
2061     }
2062
2063     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2064         board[i][holdingsColumn] = EmptySquare;
2065         board[i][countsColumn]   = (ChessSquare) 0;
2066     }
2067     while( (p=*holdings++) != NULLCHAR ) {
2068         piece = CharToPiece( ToUpper(p) );
2069         if(piece == EmptySquare) continue;
2070         /*j = (int) piece - (int) WhitePawn;*/
2071         j = PieceToNumber(piece);
2072         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2073         if(j < 0) continue;               /* should not happen */
2074         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2075         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2076         board[holdingsStartRow+j*direction][countsColumn]++;
2077     }
2078 }
2079
2080
2081 void
2082 VariantSwitch(Board board, VariantClass newVariant)
2083 {
2084    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2085    static Board oldBoard;
2086
2087    startedFromPositionFile = FALSE;
2088    if(gameInfo.variant == newVariant) return;
2089
2090    /* [HGM] This routine is called each time an assignment is made to
2091     * gameInfo.variant during a game, to make sure the board sizes
2092     * are set to match the new variant. If that means adding or deleting
2093     * holdings, we shift the playing board accordingly
2094     * This kludge is needed because in ICS observe mode, we get boards
2095     * of an ongoing game without knowing the variant, and learn about the
2096     * latter only later. This can be because of the move list we requested,
2097     * in which case the game history is refilled from the beginning anyway,
2098     * but also when receiving holdings of a crazyhouse game. In the latter
2099     * case we want to add those holdings to the already received position.
2100     */
2101
2102
2103    if (appData.debugMode) {
2104      fprintf(debugFP, "Switch board from %s to %s\n",
2105              VariantName(gameInfo.variant), VariantName(newVariant));
2106      setbuf(debugFP, NULL);
2107    }
2108    shuffleOpenings = 0;       /* [HGM] shuffle */
2109    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2110    switch(newVariant)
2111      {
2112      case VariantShogi:
2113        newWidth = 9;  newHeight = 9;
2114        gameInfo.holdingsSize = 7;
2115      case VariantBughouse:
2116      case VariantCrazyhouse:
2117        newHoldingsWidth = 2; break;
2118      case VariantGreat:
2119        newWidth = 10;
2120      case VariantSuper:
2121        newHoldingsWidth = 2;
2122        gameInfo.holdingsSize = 8;
2123        break;
2124      case VariantGothic:
2125      case VariantCapablanca:
2126      case VariantCapaRandom:
2127        newWidth = 10;
2128      default:
2129        newHoldingsWidth = gameInfo.holdingsSize = 0;
2130      };
2131
2132    if(newWidth  != gameInfo.boardWidth  ||
2133       newHeight != gameInfo.boardHeight ||
2134       newHoldingsWidth != gameInfo.holdingsWidth ) {
2135
2136      /* shift position to new playing area, if needed */
2137      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2138        for(i=0; i<BOARD_HEIGHT; i++)
2139          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2140            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2141              board[i][j];
2142        for(i=0; i<newHeight; i++) {
2143          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2144          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2145        }
2146      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2147        for(i=0; i<BOARD_HEIGHT; i++)
2148          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2149            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2150              board[i][j];
2151      }
2152      gameInfo.boardWidth  = newWidth;
2153      gameInfo.boardHeight = newHeight;
2154      gameInfo.holdingsWidth = newHoldingsWidth;
2155      gameInfo.variant = newVariant;
2156      InitDrawingSizes(-2, 0);
2157    } else gameInfo.variant = newVariant;
2158    CopyBoard(oldBoard, board);   // remember correctly formatted board
2159      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2160    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2161 }
2162
2163 static int loggedOn = FALSE;
2164
2165 /*-- Game start info cache: --*/
2166 int gs_gamenum;
2167 char gs_kind[MSG_SIZ];
2168 static char player1Name[128] = "";
2169 static char player2Name[128] = "";
2170 static char cont_seq[] = "\n\\   ";
2171 static int player1Rating = -1;
2172 static int player2Rating = -1;
2173 /*----------------------------*/
2174
2175 ColorClass curColor = ColorNormal;
2176 int suppressKibitz = 0;
2177
2178 // [HGM] seekgraph
2179 Boolean soughtPending = FALSE;
2180 Boolean seekGraphUp;
2181 #define MAX_SEEK_ADS 200
2182 #define SQUARE 0x80
2183 char *seekAdList[MAX_SEEK_ADS];
2184 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2185 float tcList[MAX_SEEK_ADS];
2186 char colorList[MAX_SEEK_ADS];
2187 int nrOfSeekAds = 0;
2188 int minRating = 1010, maxRating = 2800;
2189 int hMargin = 10, vMargin = 20, h, w;
2190 extern int squareSize, lineGap;
2191
2192 void
2193 PlotSeekAd(int i)
2194 {
2195         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2196         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2197         if(r < minRating+100 && r >=0 ) r = minRating+100;
2198         if(r > maxRating) r = maxRating;
2199         if(tc < 1.) tc = 1.;
2200         if(tc > 95.) tc = 95.;
2201         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2202         y = ((double)r - minRating)/(maxRating - minRating)
2203             * (h-vMargin-squareSize/8-1) + vMargin;
2204         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2205         if(strstr(seekAdList[i], " u ")) color = 1;
2206         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2207            !strstr(seekAdList[i], "bullet") &&
2208            !strstr(seekAdList[i], "blitz") &&
2209            !strstr(seekAdList[i], "standard") ) color = 2;
2210         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2211         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2212 }
2213
2214 void
2215 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2216 {
2217         char buf[MSG_SIZ], *ext = "";
2218         VariantClass v = StringToVariant(type);
2219         if(strstr(type, "wild")) {
2220             ext = type + 4; // append wild number
2221             if(v == VariantFischeRandom) type = "chess960"; else
2222             if(v == VariantLoadable) type = "setup"; else
2223             type = VariantName(v);
2224         }
2225         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2226         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2227             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2228             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2229             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2230             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2231             seekNrList[nrOfSeekAds] = nr;
2232             zList[nrOfSeekAds] = 0;
2233             seekAdList[nrOfSeekAds++] = StrSave(buf);
2234             if(plot) PlotSeekAd(nrOfSeekAds-1);
2235         }
2236 }
2237
2238 void
2239 EraseSeekDot(int i)
2240 {
2241     int x = xList[i], y = yList[i], d=squareSize/4, k;
2242     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2243     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2244     // now replot every dot that overlapped
2245     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2246         int xx = xList[k], yy = yList[k];
2247         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2248             DrawSeekDot(xx, yy, colorList[k]);
2249     }
2250 }
2251
2252 void
2253 RemoveSeekAd(int nr)
2254 {
2255         int i;
2256         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2257             EraseSeekDot(i);
2258             if(seekAdList[i]) free(seekAdList[i]);
2259             seekAdList[i] = seekAdList[--nrOfSeekAds];
2260             seekNrList[i] = seekNrList[nrOfSeekAds];
2261             ratingList[i] = ratingList[nrOfSeekAds];
2262             colorList[i]  = colorList[nrOfSeekAds];
2263             tcList[i] = tcList[nrOfSeekAds];
2264             xList[i]  = xList[nrOfSeekAds];
2265             yList[i]  = yList[nrOfSeekAds];
2266             zList[i]  = zList[nrOfSeekAds];
2267             seekAdList[nrOfSeekAds] = NULL;
2268             break;
2269         }
2270 }
2271
2272 Boolean
2273 MatchSoughtLine(char *line)
2274 {
2275     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2276     int nr, base, inc, u=0; char dummy;
2277
2278     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2279        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2280        (u=1) &&
2281        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2282         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2283         // match: compact and save the line
2284         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2285         return TRUE;
2286     }
2287     return FALSE;
2288 }
2289
2290 int
2291 DrawSeekGraph()
2292 {
2293     int i;
2294     if(!seekGraphUp) return FALSE;
2295     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2296     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2297
2298     DrawSeekBackground(0, 0, w, h);
2299     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2300     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2301     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2302         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2303         yy = h-1-yy;
2304         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2305         if(i%500 == 0) {
2306             char buf[MSG_SIZ];
2307             snprintf(buf, MSG_SIZ, "%d", i);
2308             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2309         }
2310     }
2311     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2312     for(i=1; i<100; i+=(i<10?1:5)) {
2313         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2314         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2315         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2316             char buf[MSG_SIZ];
2317             snprintf(buf, MSG_SIZ, "%d", i);
2318             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2319         }
2320     }
2321     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2322     return TRUE;
2323 }
2324
2325 int SeekGraphClick(ClickType click, int x, int y, int moving)
2326 {
2327     static int lastDown = 0, displayed = 0, lastSecond;
2328     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2329         if(click == Release || moving) return FALSE;
2330         nrOfSeekAds = 0;
2331         soughtPending = TRUE;
2332         SendToICS(ics_prefix);
2333         SendToICS("sought\n"); // should this be "sought all"?
2334     } else { // issue challenge based on clicked ad
2335         int dist = 10000; int i, closest = 0, second = 0;
2336         for(i=0; i<nrOfSeekAds; i++) {
2337             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2338             if(d < dist) { dist = d; closest = i; }
2339             second += (d - zList[i] < 120); // count in-range ads
2340             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2341         }
2342         if(dist < 120) {
2343             char buf[MSG_SIZ];
2344             second = (second > 1);
2345             if(displayed != closest || second != lastSecond) {
2346                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2347                 lastSecond = second; displayed = closest;
2348             }
2349             if(click == Press) {
2350                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2351                 lastDown = closest;
2352                 return TRUE;
2353             } // on press 'hit', only show info
2354             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2355             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2356             SendToICS(ics_prefix);
2357             SendToICS(buf);
2358             return TRUE; // let incoming board of started game pop down the graph
2359         } else if(click == Release) { // release 'miss' is ignored
2360             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2361             if(moving == 2) { // right up-click
2362                 nrOfSeekAds = 0; // refresh graph
2363                 soughtPending = TRUE;
2364                 SendToICS(ics_prefix);
2365                 SendToICS("sought\n"); // should this be "sought all"?
2366             }
2367             return TRUE;
2368         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2369         // press miss or release hit 'pop down' seek graph
2370         seekGraphUp = FALSE;
2371         DrawPosition(TRUE, NULL);
2372     }
2373     return TRUE;
2374 }
2375
2376 void
2377 read_from_ics(isr, closure, data, count, error)
2378      InputSourceRef isr;
2379      VOIDSTAR closure;
2380      char *data;
2381      int count;
2382      int error;
2383 {
2384 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2385 #define STARTED_NONE 0
2386 #define STARTED_MOVES 1
2387 #define STARTED_BOARD 2
2388 #define STARTED_OBSERVE 3
2389 #define STARTED_HOLDINGS 4
2390 #define STARTED_CHATTER 5
2391 #define STARTED_COMMENT 6
2392 #define STARTED_MOVES_NOHIDE 7
2393
2394     static int started = STARTED_NONE;
2395     static char parse[20000];
2396     static int parse_pos = 0;
2397     static char buf[BUF_SIZE + 1];
2398     static int firstTime = TRUE, intfSet = FALSE;
2399     static ColorClass prevColor = ColorNormal;
2400     static int savingComment = FALSE;
2401     static int cmatch = 0; // continuation sequence match
2402     char *bp;
2403     char str[MSG_SIZ];
2404     int i, oldi;
2405     int buf_len;
2406     int next_out;
2407     int tkind;
2408     int backup;    /* [DM] For zippy color lines */
2409     char *p;
2410     char talker[MSG_SIZ]; // [HGM] chat
2411     int channel;
2412
2413     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2414
2415     if (appData.debugMode) {
2416       if (!error) {
2417         fprintf(debugFP, "<ICS: ");
2418         show_bytes(debugFP, data, count);
2419         fprintf(debugFP, "\n");
2420       }
2421     }
2422
2423     if (appData.debugMode) { int f = forwardMostMove;
2424         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2425                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2426                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2427     }
2428     if (count > 0) {
2429         /* If last read ended with a partial line that we couldn't parse,
2430            prepend it to the new read and try again. */
2431         if (leftover_len > 0) {
2432             for (i=0; i<leftover_len; i++)
2433               buf[i] = buf[leftover_start + i];
2434         }
2435
2436     /* copy new characters into the buffer */
2437     bp = buf + leftover_len;
2438     buf_len=leftover_len;
2439     for (i=0; i<count; i++)
2440     {
2441         // ignore these
2442         if (data[i] == '\r')
2443             continue;
2444
2445         // join lines split by ICS?
2446         if (!appData.noJoin)
2447         {
2448             /*
2449                 Joining just consists of finding matches against the
2450                 continuation sequence, and discarding that sequence
2451                 if found instead of copying it.  So, until a match
2452                 fails, there's nothing to do since it might be the
2453                 complete sequence, and thus, something we don't want
2454                 copied.
2455             */
2456             if (data[i] == cont_seq[cmatch])
2457             {
2458                 cmatch++;
2459                 if (cmatch == strlen(cont_seq))
2460                 {
2461                     cmatch = 0; // complete match.  just reset the counter
2462
2463                     /*
2464                         it's possible for the ICS to not include the space
2465                         at the end of the last word, making our [correct]
2466                         join operation fuse two separate words.  the server
2467                         does this when the space occurs at the width setting.
2468                     */
2469                     if (!buf_len || buf[buf_len-1] != ' ')
2470                     {
2471                         *bp++ = ' ';
2472                         buf_len++;
2473                     }
2474                 }
2475                 continue;
2476             }
2477             else if (cmatch)
2478             {
2479                 /*
2480                     match failed, so we have to copy what matched before
2481                     falling through and copying this character.  In reality,
2482                     this will only ever be just the newline character, but
2483                     it doesn't hurt to be precise.
2484                 */
2485                 strncpy(bp, cont_seq, cmatch);
2486                 bp += cmatch;
2487                 buf_len += cmatch;
2488                 cmatch = 0;
2489             }
2490         }
2491
2492         // copy this char
2493         *bp++ = data[i];
2494         buf_len++;
2495     }
2496
2497         buf[buf_len] = NULLCHAR;
2498 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2499         next_out = 0;
2500         leftover_start = 0;
2501
2502         i = 0;
2503         while (i < buf_len) {
2504             /* Deal with part of the TELNET option negotiation
2505                protocol.  We refuse to do anything beyond the
2506                defaults, except that we allow the WILL ECHO option,
2507                which ICS uses to turn off password echoing when we are
2508                directly connected to it.  We reject this option
2509                if localLineEditing mode is on (always on in xboard)
2510                and we are talking to port 23, which might be a real
2511                telnet server that will try to keep WILL ECHO on permanently.
2512              */
2513             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2514                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2515                 unsigned char option;
2516                 oldi = i;
2517                 switch ((unsigned char) buf[++i]) {
2518                   case TN_WILL:
2519                     if (appData.debugMode)
2520                       fprintf(debugFP, "\n<WILL ");
2521                     switch (option = (unsigned char) buf[++i]) {
2522                       case TN_ECHO:
2523                         if (appData.debugMode)
2524                           fprintf(debugFP, "ECHO ");
2525                         /* Reply only if this is a change, according
2526                            to the protocol rules. */
2527                         if (remoteEchoOption) break;
2528                         if (appData.localLineEditing &&
2529                             atoi(appData.icsPort) == TN_PORT) {
2530                             TelnetRequest(TN_DONT, TN_ECHO);
2531                         } else {
2532                             EchoOff();
2533                             TelnetRequest(TN_DO, TN_ECHO);
2534                             remoteEchoOption = TRUE;
2535                         }
2536                         break;
2537                       default:
2538                         if (appData.debugMode)
2539                           fprintf(debugFP, "%d ", option);
2540                         /* Whatever this is, we don't want it. */
2541                         TelnetRequest(TN_DONT, option);
2542                         break;
2543                     }
2544                     break;
2545                   case TN_WONT:
2546                     if (appData.debugMode)
2547                       fprintf(debugFP, "\n<WONT ");
2548                     switch (option = (unsigned char) buf[++i]) {
2549                       case TN_ECHO:
2550                         if (appData.debugMode)
2551                           fprintf(debugFP, "ECHO ");
2552                         /* Reply only if this is a change, according
2553                            to the protocol rules. */
2554                         if (!remoteEchoOption) break;
2555                         EchoOn();
2556                         TelnetRequest(TN_DONT, TN_ECHO);
2557                         remoteEchoOption = FALSE;
2558                         break;
2559                       default:
2560                         if (appData.debugMode)
2561                           fprintf(debugFP, "%d ", (unsigned char) option);
2562                         /* Whatever this is, it must already be turned
2563                            off, because we never agree to turn on
2564                            anything non-default, so according to the
2565                            protocol rules, we don't reply. */
2566                         break;
2567                     }
2568                     break;
2569                   case TN_DO:
2570                     if (appData.debugMode)
2571                       fprintf(debugFP, "\n<DO ");
2572                     switch (option = (unsigned char) buf[++i]) {
2573                       default:
2574                         /* Whatever this is, we refuse to do it. */
2575                         if (appData.debugMode)
2576                           fprintf(debugFP, "%d ", option);
2577                         TelnetRequest(TN_WONT, option);
2578                         break;
2579                     }
2580                     break;
2581                   case TN_DONT:
2582                     if (appData.debugMode)
2583                       fprintf(debugFP, "\n<DONT ");
2584                     switch (option = (unsigned char) buf[++i]) {
2585                       default:
2586                         if (appData.debugMode)
2587                           fprintf(debugFP, "%d ", option);
2588                         /* Whatever this is, we are already not doing
2589                            it, because we never agree to do anything
2590                            non-default, so according to the protocol
2591                            rules, we don't reply. */
2592                         break;
2593                     }
2594                     break;
2595                   case TN_IAC:
2596                     if (appData.debugMode)
2597                       fprintf(debugFP, "\n<IAC ");
2598                     /* Doubled IAC; pass it through */
2599                     i--;
2600                     break;
2601                   default:
2602                     if (appData.debugMode)
2603                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2604                     /* Drop all other telnet commands on the floor */
2605                     break;
2606                 }
2607                 if (oldi > next_out)
2608                   SendToPlayer(&buf[next_out], oldi - next_out);
2609                 if (++i > next_out)
2610                   next_out = i;
2611                 continue;
2612             }
2613
2614             /* OK, this at least will *usually* work */
2615             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2616                 loggedOn = TRUE;
2617             }
2618
2619             if (loggedOn && !intfSet) {
2620                 if (ics_type == ICS_ICC) {
2621                   snprintf(str, MSG_SIZ,
2622                           "/set-quietly interface %s\n/set-quietly style 12\n",
2623                           programVersion);
2624                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2625                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2626                 } else if (ics_type == ICS_CHESSNET) {
2627                   snprintf(str, MSG_SIZ, "/style 12\n");
2628                 } else {
2629                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2630                   strcat(str, programVersion);
2631                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2632                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2633                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2634 #ifdef WIN32
2635                   strcat(str, "$iset nohighlight 1\n");
2636 #endif
2637                   strcat(str, "$iset lock 1\n$style 12\n");
2638                 }
2639                 SendToICS(str);
2640                 NotifyFrontendLogin();
2641                 intfSet = TRUE;
2642             }
2643
2644             if (started == STARTED_COMMENT) {
2645                 /* Accumulate characters in comment */
2646                 parse[parse_pos++] = buf[i];
2647                 if (buf[i] == '\n') {
2648                     parse[parse_pos] = NULLCHAR;
2649                     if(chattingPartner>=0) {
2650                         char mess[MSG_SIZ];
2651                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2652                         OutputChatMessage(chattingPartner, mess);
2653                         chattingPartner = -1;
2654                         next_out = i+1; // [HGM] suppress printing in ICS window
2655                     } else
2656                     if(!suppressKibitz) // [HGM] kibitz
2657                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2658                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2659                         int nrDigit = 0, nrAlph = 0, j;
2660                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2661                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2662                         parse[parse_pos] = NULLCHAR;
2663                         // try to be smart: if it does not look like search info, it should go to
2664                         // ICS interaction window after all, not to engine-output window.
2665                         for(j=0; j<parse_pos; j++) { // count letters and digits
2666                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2667                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2668                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2669                         }
2670                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2671                             int depth=0; float score;
2672                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2673                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2674                                 pvInfoList[forwardMostMove-1].depth = depth;
2675                                 pvInfoList[forwardMostMove-1].score = 100*score;
2676                             }
2677                             OutputKibitz(suppressKibitz, parse);
2678                         } else {
2679                             char tmp[MSG_SIZ];
2680                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2681                             SendToPlayer(tmp, strlen(tmp));
2682                         }
2683                         next_out = i+1; // [HGM] suppress printing in ICS window
2684                     }
2685                     started = STARTED_NONE;
2686                 } else {
2687                     /* Don't match patterns against characters in comment */
2688                     i++;
2689                     continue;
2690                 }
2691             }
2692             if (started == STARTED_CHATTER) {
2693                 if (buf[i] != '\n') {
2694                     /* Don't match patterns against characters in chatter */
2695                     i++;
2696                     continue;
2697                 }
2698                 started = STARTED_NONE;
2699                 if(suppressKibitz) next_out = i+1;
2700             }
2701
2702             /* Kludge to deal with rcmd protocol */
2703             if (firstTime && looking_at(buf, &i, "\001*")) {
2704                 DisplayFatalError(&buf[1], 0, 1);
2705                 continue;
2706             } else {
2707                 firstTime = FALSE;
2708             }
2709
2710             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2711                 ics_type = ICS_ICC;
2712                 ics_prefix = "/";
2713                 if (appData.debugMode)
2714                   fprintf(debugFP, "ics_type %d\n", ics_type);
2715                 continue;
2716             }
2717             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2718                 ics_type = ICS_FICS;
2719                 ics_prefix = "$";
2720                 if (appData.debugMode)
2721                   fprintf(debugFP, "ics_type %d\n", ics_type);
2722                 continue;
2723             }
2724             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2725                 ics_type = ICS_CHESSNET;
2726                 ics_prefix = "/";
2727                 if (appData.debugMode)
2728                   fprintf(debugFP, "ics_type %d\n", ics_type);
2729                 continue;
2730             }
2731
2732             if (!loggedOn &&
2733                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2734                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2735                  looking_at(buf, &i, "will be \"*\""))) {
2736               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2737               continue;
2738             }
2739
2740             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2741               char buf[MSG_SIZ];
2742               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2743               DisplayIcsInteractionTitle(buf);
2744               have_set_title = TRUE;
2745             }
2746
2747             /* skip finger notes */
2748             if (started == STARTED_NONE &&
2749                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2750                  (buf[i] == '1' && buf[i+1] == '0')) &&
2751                 buf[i+2] == ':' && buf[i+3] == ' ') {
2752               started = STARTED_CHATTER;
2753               i += 3;
2754               continue;
2755             }
2756
2757             oldi = i;
2758             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2759             if(appData.seekGraph) {
2760                 if(soughtPending && MatchSoughtLine(buf+i)) {
2761                     i = strstr(buf+i, "rated") - buf;
2762                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2763                     next_out = leftover_start = i;
2764                     started = STARTED_CHATTER;
2765                     suppressKibitz = TRUE;
2766                     continue;
2767                 }
2768                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2769                         && looking_at(buf, &i, "* ads displayed")) {
2770                     soughtPending = FALSE;
2771                     seekGraphUp = TRUE;
2772                     DrawSeekGraph();
2773                     continue;
2774                 }
2775                 if(appData.autoRefresh) {
2776                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2777                         int s = (ics_type == ICS_ICC); // ICC format differs
2778                         if(seekGraphUp)
2779                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2780                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2781                         looking_at(buf, &i, "*% "); // eat prompt
2782                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2783                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2784                         next_out = i; // suppress
2785                         continue;
2786                     }
2787                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2788                         char *p = star_match[0];
2789                         while(*p) {
2790                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2791                             while(*p && *p++ != ' '); // next
2792                         }
2793                         looking_at(buf, &i, "*% "); // eat prompt
2794                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2795                         next_out = i;
2796                         continue;
2797                     }
2798                 }
2799             }
2800
2801             /* skip formula vars */
2802             if (started == STARTED_NONE &&
2803                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2804               started = STARTED_CHATTER;
2805               i += 3;
2806               continue;
2807             }
2808
2809             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2810             if (appData.autoKibitz && started == STARTED_NONE &&
2811                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2812                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2813                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2814                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2815                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2816                         suppressKibitz = TRUE;
2817                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2818                         next_out = i;
2819                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2820                                 && (gameMode == IcsPlayingWhite)) ||
2821                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2822                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2823                             started = STARTED_CHATTER; // own kibitz we simply discard
2824                         else {
2825                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2826                             parse_pos = 0; parse[0] = NULLCHAR;
2827                             savingComment = TRUE;
2828                             suppressKibitz = gameMode != IcsObserving ? 2 :
2829                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2830                         }
2831                         continue;
2832                 } else
2833                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2834                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2835                          && atoi(star_match[0])) {
2836                     // suppress the acknowledgements of our own autoKibitz
2837                     char *p;
2838                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2839                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2840                     SendToPlayer(star_match[0], strlen(star_match[0]));
2841                     if(looking_at(buf, &i, "*% ")) // eat prompt
2842                         suppressKibitz = FALSE;
2843                     next_out = i;
2844                     continue;
2845                 }
2846             } // [HGM] kibitz: end of patch
2847
2848             // [HGM] chat: intercept tells by users for which we have an open chat window
2849             channel = -1;
2850             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2851                                            looking_at(buf, &i, "* whispers:") ||
2852                                            looking_at(buf, &i, "* kibitzes:") ||
2853                                            looking_at(buf, &i, "* shouts:") ||
2854                                            looking_at(buf, &i, "* c-shouts:") ||
2855                                            looking_at(buf, &i, "--> * ") ||
2856                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2857                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2858                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2859                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2860                 int p;
2861                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2862                 chattingPartner = -1;
2863
2864                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2865                 for(p=0; p<MAX_CHAT; p++) {
2866                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2867                     talker[0] = '['; strcat(talker, "] ");
2868                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2869                     chattingPartner = p; break;
2870                     }
2871                 } else
2872                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2873                 for(p=0; p<MAX_CHAT; p++) {
2874                     if(!strcmp("kibitzes", chatPartner[p])) {
2875                         talker[0] = '['; strcat(talker, "] ");
2876                         chattingPartner = p; break;
2877                     }
2878                 } else
2879                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2880                 for(p=0; p<MAX_CHAT; p++) {
2881                     if(!strcmp("whispers", chatPartner[p])) {
2882                         talker[0] = '['; strcat(talker, "] ");
2883                         chattingPartner = p; break;
2884                     }
2885                 } else
2886                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2887                   if(buf[i-8] == '-' && buf[i-3] == 't')
2888                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2889                     if(!strcmp("c-shouts", chatPartner[p])) {
2890                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2891                         chattingPartner = p; break;
2892                     }
2893                   }
2894                   if(chattingPartner < 0)
2895                   for(p=0; p<MAX_CHAT; p++) {
2896                     if(!strcmp("shouts", chatPartner[p])) {
2897                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2898                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2899                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2900                         chattingPartner = p; break;
2901                     }
2902                   }
2903                 }
2904                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2905                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2906                     talker[0] = 0; Colorize(ColorTell, FALSE);
2907                     chattingPartner = p; break;
2908                 }
2909                 if(chattingPartner<0) i = oldi; else {
2910                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2911                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2912                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2913                     started = STARTED_COMMENT;
2914                     parse_pos = 0; parse[0] = NULLCHAR;
2915                     savingComment = 3 + chattingPartner; // counts as TRUE
2916                     suppressKibitz = TRUE;
2917                     continue;
2918                 }
2919             } // [HGM] chat: end of patch
2920
2921             if (appData.zippyTalk || appData.zippyPlay) {
2922                 /* [DM] Backup address for color zippy lines */
2923                 backup = i;
2924 #if ZIPPY
2925                if (loggedOn == TRUE)
2926                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2927                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2928 #endif
2929             } // [DM] 'else { ' deleted
2930                 if (
2931                     /* Regular tells and says */
2932                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2933                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2934                     looking_at(buf, &i, "* says: ") ||
2935                     /* Don't color "message" or "messages" output */
2936                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2937                     looking_at(buf, &i, "*. * at *:*: ") ||
2938                     looking_at(buf, &i, "--* (*:*): ") ||
2939                     /* Message notifications (same color as tells) */
2940                     looking_at(buf, &i, "* has left a message ") ||
2941                     looking_at(buf, &i, "* just sent you a message:\n") ||
2942                     /* Whispers and kibitzes */
2943                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2944                     looking_at(buf, &i, "* kibitzes: ") ||
2945                     /* Channel tells */
2946                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2947
2948                   if (tkind == 1 && strchr(star_match[0], ':')) {
2949                       /* Avoid "tells you:" spoofs in channels */
2950                      tkind = 3;
2951                   }
2952                   if (star_match[0][0] == NULLCHAR ||
2953                       strchr(star_match[0], ' ') ||
2954                       (tkind == 3 && strchr(star_match[1], ' '))) {
2955                     /* Reject bogus matches */
2956                     i = oldi;
2957                   } else {
2958                     if (appData.colorize) {
2959                       if (oldi > next_out) {
2960                         SendToPlayer(&buf[next_out], oldi - next_out);
2961                         next_out = oldi;
2962                       }
2963                       switch (tkind) {
2964                       case 1:
2965                         Colorize(ColorTell, FALSE);
2966                         curColor = ColorTell;
2967                         break;
2968                       case 2:
2969                         Colorize(ColorKibitz, FALSE);
2970                         curColor = ColorKibitz;
2971                         break;
2972                       case 3:
2973                         p = strrchr(star_match[1], '(');
2974                         if (p == NULL) {
2975                           p = star_match[1];
2976                         } else {
2977                           p++;
2978                         }
2979                         if (atoi(p) == 1) {
2980                           Colorize(ColorChannel1, FALSE);
2981                           curColor = ColorChannel1;
2982                         } else {
2983                           Colorize(ColorChannel, FALSE);
2984                           curColor = ColorChannel;
2985                         }
2986                         break;
2987                       case 5:
2988                         curColor = ColorNormal;
2989                         break;
2990                       }
2991                     }
2992                     if (started == STARTED_NONE && appData.autoComment &&
2993                         (gameMode == IcsObserving ||
2994                          gameMode == IcsPlayingWhite ||
2995                          gameMode == IcsPlayingBlack)) {
2996                       parse_pos = i - oldi;
2997                       memcpy(parse, &buf[oldi], parse_pos);
2998                       parse[parse_pos] = NULLCHAR;
2999                       started = STARTED_COMMENT;
3000                       savingComment = TRUE;
3001                     } else {
3002                       started = STARTED_CHATTER;
3003                       savingComment = FALSE;
3004                     }
3005                     loggedOn = TRUE;
3006                     continue;
3007                   }
3008                 }
3009
3010                 if (looking_at(buf, &i, "* s-shouts: ") ||
3011                     looking_at(buf, &i, "* c-shouts: ")) {
3012                     if (appData.colorize) {
3013                         if (oldi > next_out) {
3014                             SendToPlayer(&buf[next_out], oldi - next_out);
3015                             next_out = oldi;
3016                         }
3017                         Colorize(ColorSShout, FALSE);
3018                         curColor = ColorSShout;
3019                     }
3020                     loggedOn = TRUE;
3021                     started = STARTED_CHATTER;
3022                     continue;
3023                 }
3024
3025                 if (looking_at(buf, &i, "--->")) {
3026                     loggedOn = TRUE;
3027                     continue;
3028                 }
3029
3030                 if (looking_at(buf, &i, "* shouts: ") ||
3031                     looking_at(buf, &i, "--> ")) {
3032                     if (appData.colorize) {
3033                         if (oldi > next_out) {
3034                             SendToPlayer(&buf[next_out], oldi - next_out);
3035                             next_out = oldi;
3036                         }
3037                         Colorize(ColorShout, FALSE);
3038                         curColor = ColorShout;
3039                     }
3040                     loggedOn = TRUE;
3041                     started = STARTED_CHATTER;
3042                     continue;
3043                 }
3044
3045                 if (looking_at( buf, &i, "Challenge:")) {
3046                     if (appData.colorize) {
3047                         if (oldi > next_out) {
3048                             SendToPlayer(&buf[next_out], oldi - next_out);
3049                             next_out = oldi;
3050                         }
3051                         Colorize(ColorChallenge, FALSE);
3052                         curColor = ColorChallenge;
3053                     }
3054                     loggedOn = TRUE;
3055                     continue;
3056                 }
3057
3058                 if (looking_at(buf, &i, "* offers you") ||
3059                     looking_at(buf, &i, "* offers to be") ||
3060                     looking_at(buf, &i, "* would like to") ||
3061                     looking_at(buf, &i, "* requests to") ||
3062                     looking_at(buf, &i, "Your opponent offers") ||
3063                     looking_at(buf, &i, "Your opponent requests")) {
3064
3065                     if (appData.colorize) {
3066                         if (oldi > next_out) {
3067                             SendToPlayer(&buf[next_out], oldi - next_out);
3068                             next_out = oldi;
3069                         }
3070                         Colorize(ColorRequest, FALSE);
3071                         curColor = ColorRequest;
3072                     }
3073                     continue;
3074                 }
3075
3076                 if (looking_at(buf, &i, "* (*) seeking")) {
3077                     if (appData.colorize) {
3078                         if (oldi > next_out) {
3079                             SendToPlayer(&buf[next_out], oldi - next_out);
3080                             next_out = oldi;
3081                         }
3082                         Colorize(ColorSeek, FALSE);
3083                         curColor = ColorSeek;
3084                     }
3085                     continue;
3086             }
3087
3088             if (looking_at(buf, &i, "\\   ")) {
3089                 if (prevColor != ColorNormal) {
3090                     if (oldi > next_out) {
3091                         SendToPlayer(&buf[next_out], oldi - next_out);
3092                         next_out = oldi;
3093                     }
3094                     Colorize(prevColor, TRUE);
3095                     curColor = prevColor;
3096                 }
3097                 if (savingComment) {
3098                     parse_pos = i - oldi;
3099                     memcpy(parse, &buf[oldi], parse_pos);
3100                     parse[parse_pos] = NULLCHAR;
3101                     started = STARTED_COMMENT;
3102                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3103                         chattingPartner = savingComment - 3; // kludge to remember the box
3104                 } else {
3105                     started = STARTED_CHATTER;
3106                 }
3107                 continue;
3108             }
3109
3110             if (looking_at(buf, &i, "Black Strength :") ||
3111                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3112                 looking_at(buf, &i, "<10>") ||
3113                 looking_at(buf, &i, "#@#")) {
3114                 /* Wrong board style */
3115                 loggedOn = TRUE;
3116                 SendToICS(ics_prefix);
3117                 SendToICS("set style 12\n");
3118                 SendToICS(ics_prefix);
3119                 SendToICS("refresh\n");
3120                 continue;
3121             }
3122
3123             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3124                 ICSInitScript();
3125                 have_sent_ICS_logon = 1;
3126                 continue;
3127             }
3128
3129             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3130                 (looking_at(buf, &i, "\n<12> ") ||
3131                  looking_at(buf, &i, "<12> "))) {
3132                 loggedOn = TRUE;
3133                 if (oldi > next_out) {
3134                     SendToPlayer(&buf[next_out], oldi - next_out);
3135                 }
3136                 next_out = i;
3137                 started = STARTED_BOARD;
3138                 parse_pos = 0;
3139                 continue;
3140             }
3141
3142             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3143                 looking_at(buf, &i, "<b1> ")) {
3144                 if (oldi > next_out) {
3145                     SendToPlayer(&buf[next_out], oldi - next_out);
3146                 }
3147                 next_out = i;
3148                 started = STARTED_HOLDINGS;
3149                 parse_pos = 0;
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3154                 loggedOn = TRUE;
3155                 /* Header for a move list -- first line */
3156
3157                 switch (ics_getting_history) {
3158                   case H_FALSE:
3159                     switch (gameMode) {
3160                       case IcsIdle:
3161                       case BeginningOfGame:
3162                         /* User typed "moves" or "oldmoves" while we
3163                            were idle.  Pretend we asked for these
3164                            moves and soak them up so user can step
3165                            through them and/or save them.
3166                            */
3167                         Reset(FALSE, TRUE);
3168                         gameMode = IcsObserving;
3169                         ModeHighlight();
3170                         ics_gamenum = -1;
3171                         ics_getting_history = H_GOT_UNREQ_HEADER;
3172                         break;
3173                       case EditGame: /*?*/
3174                       case EditPosition: /*?*/
3175                         /* Should above feature work in these modes too? */
3176                         /* For now it doesn't */
3177                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3178                         break;
3179                       default:
3180                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3181                         break;
3182                     }
3183                     break;
3184                   case H_REQUESTED:
3185                     /* Is this the right one? */
3186                     if (gameInfo.white && gameInfo.black &&
3187                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3188                         strcmp(gameInfo.black, star_match[2]) == 0) {
3189                         /* All is well */
3190                         ics_getting_history = H_GOT_REQ_HEADER;
3191                     }
3192                     break;
3193                   case H_GOT_REQ_HEADER:
3194                   case H_GOT_UNREQ_HEADER:
3195                   case H_GOT_UNWANTED_HEADER:
3196                   case H_GETTING_MOVES:
3197                     /* Should not happen */
3198                     DisplayError(_("Error gathering move list: two headers"), 0);
3199                     ics_getting_history = H_FALSE;
3200                     break;
3201                 }
3202
3203                 /* Save player ratings into gameInfo if needed */
3204                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3205                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3206                     (gameInfo.whiteRating == -1 ||
3207                      gameInfo.blackRating == -1)) {
3208
3209                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3210                     gameInfo.blackRating = string_to_rating(star_match[3]);
3211                     if (appData.debugMode)
3212                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3213                               gameInfo.whiteRating, gameInfo.blackRating);
3214                 }
3215                 continue;
3216             }
3217
3218             if (looking_at(buf, &i,
3219               "* * match, initial time: * minute*, increment: * second")) {
3220                 /* Header for a move list -- second line */
3221                 /* Initial board will follow if this is a wild game */
3222                 if (gameInfo.event != NULL) free(gameInfo.event);
3223                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3224                 gameInfo.event = StrSave(str);
3225                 /* [HGM] we switched variant. Translate boards if needed. */
3226                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3227                 continue;
3228             }
3229
3230             if (looking_at(buf, &i, "Move  ")) {
3231                 /* Beginning of a move list */
3232                 switch (ics_getting_history) {
3233                   case H_FALSE:
3234                     /* Normally should not happen */
3235                     /* Maybe user hit reset while we were parsing */
3236                     break;
3237                   case H_REQUESTED:
3238                     /* Happens if we are ignoring a move list that is not
3239                      * the one we just requested.  Common if the user
3240                      * tries to observe two games without turning off
3241                      * getMoveList */
3242                     break;
3243                   case H_GETTING_MOVES:
3244                     /* Should not happen */
3245                     DisplayError(_("Error gathering move list: nested"), 0);
3246                     ics_getting_history = H_FALSE;
3247                     break;
3248                   case H_GOT_REQ_HEADER:
3249                     ics_getting_history = H_GETTING_MOVES;
3250                     started = STARTED_MOVES;
3251                     parse_pos = 0;
3252                     if (oldi > next_out) {
3253                         SendToPlayer(&buf[next_out], oldi - next_out);
3254                     }
3255                     break;
3256                   case H_GOT_UNREQ_HEADER:
3257                     ics_getting_history = H_GETTING_MOVES;
3258                     started = STARTED_MOVES_NOHIDE;
3259                     parse_pos = 0;
3260                     break;
3261                   case H_GOT_UNWANTED_HEADER:
3262                     ics_getting_history = H_FALSE;
3263                     break;
3264                 }
3265                 continue;
3266             }
3267
3268             if (looking_at(buf, &i, "% ") ||
3269                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3270                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3271                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3272                     soughtPending = FALSE;
3273                     seekGraphUp = TRUE;
3274                     DrawSeekGraph();
3275                 }
3276                 if(suppressKibitz) next_out = i;
3277                 savingComment = FALSE;
3278                 suppressKibitz = 0;
3279                 switch (started) {
3280                   case STARTED_MOVES:
3281                   case STARTED_MOVES_NOHIDE:
3282                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3283                     parse[parse_pos + i - oldi] = NULLCHAR;
3284                     ParseGameHistory(parse);
3285 #if ZIPPY
3286                     if (appData.zippyPlay && first.initDone) {
3287                         FeedMovesToProgram(&first, forwardMostMove);
3288                         if (gameMode == IcsPlayingWhite) {
3289                             if (WhiteOnMove(forwardMostMove)) {
3290                                 if (first.sendTime) {
3291                                   if (first.useColors) {
3292                                     SendToProgram("black\n", &first);
3293                                   }
3294                                   SendTimeRemaining(&first, TRUE);
3295                                 }
3296                                 if (first.useColors) {
3297                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3298                                 }
3299                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3300                                 first.maybeThinking = TRUE;
3301                             } else {
3302                                 if (first.usePlayother) {
3303                                   if (first.sendTime) {
3304                                     SendTimeRemaining(&first, TRUE);
3305                                   }
3306                                   SendToProgram("playother\n", &first);
3307                                   firstMove = FALSE;
3308                                 } else {
3309                                   firstMove = TRUE;
3310                                 }
3311                             }
3312                         } else if (gameMode == IcsPlayingBlack) {
3313                             if (!WhiteOnMove(forwardMostMove)) {
3314                                 if (first.sendTime) {
3315                                   if (first.useColors) {
3316                                     SendToProgram("white\n", &first);
3317                                   }
3318                                   SendTimeRemaining(&first, FALSE);
3319                                 }
3320                                 if (first.useColors) {
3321                                   SendToProgram("black\n", &first);
3322                                 }
3323                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3324                                 first.maybeThinking = TRUE;
3325                             } else {
3326                                 if (first.usePlayother) {
3327                                   if (first.sendTime) {
3328                                     SendTimeRemaining(&first, FALSE);
3329                                   }
3330                                   SendToProgram("playother\n", &first);
3331                                   firstMove = FALSE;
3332                                 } else {
3333                                   firstMove = TRUE;
3334                                 }
3335                             }
3336                         }
3337                     }
3338 #endif
3339                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3340                         /* Moves came from oldmoves or moves command
3341                            while we weren't doing anything else.
3342                            */
3343                         currentMove = forwardMostMove;
3344                         ClearHighlights();/*!!could figure this out*/
3345                         flipView = appData.flipView;
3346                         DrawPosition(TRUE, boards[currentMove]);
3347                         DisplayBothClocks();
3348                         snprintf(str, MSG_SIZ, "%s vs. %s",
3349                                 gameInfo.white, gameInfo.black);
3350                         DisplayTitle(str);
3351                         gameMode = IcsIdle;
3352                     } else {
3353                         /* Moves were history of an active game */
3354                         if (gameInfo.resultDetails != NULL) {
3355                             free(gameInfo.resultDetails);
3356                             gameInfo.resultDetails = NULL;
3357                         }
3358                     }
3359                     HistorySet(parseList, backwardMostMove,
3360                                forwardMostMove, currentMove-1);
3361                     DisplayMove(currentMove - 1);
3362                     if (started == STARTED_MOVES) next_out = i;
3363                     started = STARTED_NONE;
3364                     ics_getting_history = H_FALSE;
3365                     break;
3366
3367                   case STARTED_OBSERVE:
3368                     started = STARTED_NONE;
3369                     SendToICS(ics_prefix);
3370                     SendToICS("refresh\n");
3371                     break;
3372
3373                   default:
3374                     break;
3375                 }
3376                 if(bookHit) { // [HGM] book: simulate book reply
3377                     static char bookMove[MSG_SIZ]; // a bit generous?
3378
3379                     programStats.nodes = programStats.depth = programStats.time =
3380                     programStats.score = programStats.got_only_move = 0;
3381                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3382
3383                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3384                     strcat(bookMove, bookHit);
3385                     HandleMachineMove(bookMove, &first);
3386                 }
3387                 continue;
3388             }
3389
3390             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3391                  started == STARTED_HOLDINGS ||
3392                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3393                 /* Accumulate characters in move list or board */
3394                 parse[parse_pos++] = buf[i];
3395             }
3396
3397             /* Start of game messages.  Mostly we detect start of game
3398                when the first board image arrives.  On some versions
3399                of the ICS, though, we need to do a "refresh" after starting
3400                to observe in order to get the current board right away. */
3401             if (looking_at(buf, &i, "Adding game * to observation list")) {
3402                 started = STARTED_OBSERVE;
3403                 continue;
3404             }
3405
3406             /* Handle auto-observe */
3407             if (appData.autoObserve &&
3408                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3409                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3410                 char *player;
3411                 /* Choose the player that was highlighted, if any. */
3412                 if (star_match[0][0] == '\033' ||
3413                     star_match[1][0] != '\033') {
3414                     player = star_match[0];
3415                 } else {
3416                     player = star_match[2];
3417                 }
3418                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3419                         ics_prefix, StripHighlightAndTitle(player));
3420                 SendToICS(str);
3421
3422                 /* Save ratings from notify string */
3423                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3424                 player1Rating = string_to_rating(star_match[1]);
3425                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3426                 player2Rating = string_to_rating(star_match[3]);
3427
3428                 if (appData.debugMode)
3429                   fprintf(debugFP,
3430                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3431                           player1Name, player1Rating,
3432                           player2Name, player2Rating);
3433
3434                 continue;
3435             }
3436
3437             /* Deal with automatic examine mode after a game,
3438                and with IcsObserving -> IcsExamining transition */
3439             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3440                 looking_at(buf, &i, "has made you an examiner of game *")) {
3441
3442                 int gamenum = atoi(star_match[0]);
3443                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3444                     gamenum == ics_gamenum) {
3445                     /* We were already playing or observing this game;
3446                        no need to refetch history */
3447                     gameMode = IcsExamining;
3448                     if (pausing) {
3449                         pauseExamForwardMostMove = forwardMostMove;
3450                     } else if (currentMove < forwardMostMove) {
3451                         ForwardInner(forwardMostMove);
3452                     }
3453                 } else {
3454                     /* I don't think this case really can happen */
3455                     SendToICS(ics_prefix);
3456                     SendToICS("refresh\n");
3457                 }
3458                 continue;
3459             }
3460
3461             /* Error messages */
3462 //          if (ics_user_moved) {
3463             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3464                 if (looking_at(buf, &i, "Illegal move") ||
3465                     looking_at(buf, &i, "Not a legal move") ||
3466                     looking_at(buf, &i, "Your king is in check") ||
3467                     looking_at(buf, &i, "It isn't your turn") ||
3468                     looking_at(buf, &i, "It is not your move")) {
3469                     /* Illegal move */
3470                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3471                         currentMove = forwardMostMove-1;
3472                         DisplayMove(currentMove - 1); /* before DMError */
3473                         DrawPosition(FALSE, boards[currentMove]);
3474                         SwitchClocks(forwardMostMove-1); // [HGM] race
3475                         DisplayBothClocks();
3476                     }
3477                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3478                     ics_user_moved = 0;
3479                     continue;
3480                 }
3481             }
3482
3483             if (looking_at(buf, &i, "still have time") ||
3484                 looking_at(buf, &i, "not out of time") ||
3485                 looking_at(buf, &i, "either player is out of time") ||
3486                 looking_at(buf, &i, "has timeseal; checking")) {
3487                 /* We must have called his flag a little too soon */
3488                 whiteFlag = blackFlag = FALSE;
3489                 continue;
3490             }
3491
3492             if (looking_at(buf, &i, "added * seconds to") ||
3493                 looking_at(buf, &i, "seconds were added to")) {
3494                 /* Update the clocks */
3495                 SendToICS(ics_prefix);
3496                 SendToICS("refresh\n");
3497                 continue;
3498             }
3499
3500             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3501                 ics_clock_paused = TRUE;
3502                 StopClocks();
3503                 continue;
3504             }
3505
3506             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3507                 ics_clock_paused = FALSE;
3508                 StartClocks();
3509                 continue;
3510             }
3511
3512             /* Grab player ratings from the Creating: message.
3513                Note we have to check for the special case when
3514                the ICS inserts things like [white] or [black]. */
3515             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3516                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3517                 /* star_matches:
3518                    0    player 1 name (not necessarily white)
3519                    1    player 1 rating
3520                    2    empty, white, or black (IGNORED)
3521                    3    player 2 name (not necessarily black)
3522                    4    player 2 rating
3523
3524                    The names/ratings are sorted out when the game
3525                    actually starts (below).
3526                 */
3527                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3528                 player1Rating = string_to_rating(star_match[1]);
3529                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3530                 player2Rating = string_to_rating(star_match[4]);
3531
3532                 if (appData.debugMode)
3533                   fprintf(debugFP,
3534                           "Ratings from 'Creating:' %s %d, %s %d\n",
3535                           player1Name, player1Rating,
3536                           player2Name, player2Rating);
3537
3538                 continue;
3539             }
3540
3541             /* Improved generic start/end-of-game messages */
3542             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3543                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3544                 /* If tkind == 0: */
3545                 /* star_match[0] is the game number */
3546                 /*           [1] is the white player's name */
3547                 /*           [2] is the black player's name */
3548                 /* For end-of-game: */
3549                 /*           [3] is the reason for the game end */
3550                 /*           [4] is a PGN end game-token, preceded by " " */
3551                 /* For start-of-game: */
3552                 /*           [3] begins with "Creating" or "Continuing" */
3553                 /*           [4] is " *" or empty (don't care). */
3554                 int gamenum = atoi(star_match[0]);
3555                 char *whitename, *blackname, *why, *endtoken;
3556                 ChessMove endtype = EndOfFile;
3557
3558                 if (tkind == 0) {
3559                   whitename = star_match[1];
3560                   blackname = star_match[2];
3561                   why = star_match[3];
3562                   endtoken = star_match[4];
3563                 } else {
3564                   whitename = star_match[1];
3565                   blackname = star_match[3];
3566                   why = star_match[5];
3567                   endtoken = star_match[6];
3568                 }
3569
3570                 /* Game start messages */
3571                 if (strncmp(why, "Creating ", 9) == 0 ||
3572                     strncmp(why, "Continuing ", 11) == 0) {
3573                     gs_gamenum = gamenum;
3574                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3575                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3576 #if ZIPPY
3577                     if (appData.zippyPlay) {
3578                         ZippyGameStart(whitename, blackname);
3579                     }
3580 #endif /*ZIPPY*/
3581                     partnerBoardValid = FALSE; // [HGM] bughouse
3582                     continue;
3583                 }
3584
3585                 /* Game end messages */
3586                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3587                     ics_gamenum != gamenum) {
3588                     continue;
3589                 }
3590                 while (endtoken[0] == ' ') endtoken++;
3591                 switch (endtoken[0]) {
3592                   case '*':
3593                   default:
3594                     endtype = GameUnfinished;
3595                     break;
3596                   case '0':
3597                     endtype = BlackWins;
3598                     break;
3599                   case '1':
3600                     if (endtoken[1] == '/')
3601                       endtype = GameIsDrawn;
3602                     else
3603                       endtype = WhiteWins;
3604                     break;
3605                 }
3606                 GameEnds(endtype, why, GE_ICS);
3607 #if ZIPPY
3608                 if (appData.zippyPlay && first.initDone) {
3609                     ZippyGameEnd(endtype, why);
3610                     if (first.pr == NULL) {
3611                       /* Start the next process early so that we'll
3612                          be ready for the next challenge */
3613                       StartChessProgram(&first);
3614                     }
3615                     /* Send "new" early, in case this command takes
3616                        a long time to finish, so that we'll be ready
3617                        for the next challenge. */
3618                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3619                     Reset(TRUE, TRUE);
3620                 }
3621 #endif /*ZIPPY*/
3622                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "Removing game * from observation") ||
3627                 looking_at(buf, &i, "no longer observing game *") ||
3628                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3629                 if (gameMode == IcsObserving &&
3630                     atoi(star_match[0]) == ics_gamenum)
3631                   {
3632                       /* icsEngineAnalyze */
3633                       if (appData.icsEngineAnalyze) {
3634                             ExitAnalyzeMode();
3635                             ModeHighlight();
3636                       }
3637                       StopClocks();
3638                       gameMode = IcsIdle;
3639                       ics_gamenum = -1;
3640                       ics_user_moved = FALSE;
3641                   }
3642                 continue;
3643             }
3644
3645             if (looking_at(buf, &i, "no longer examining game *")) {
3646                 if (gameMode == IcsExamining &&
3647                     atoi(star_match[0]) == ics_gamenum)
3648                   {
3649                       gameMode = IcsIdle;
3650                       ics_gamenum = -1;
3651                       ics_user_moved = FALSE;
3652                   }
3653                 continue;
3654             }
3655
3656             /* Advance leftover_start past any newlines we find,
3657                so only partial lines can get reparsed */
3658             if (looking_at(buf, &i, "\n")) {
3659                 prevColor = curColor;
3660                 if (curColor != ColorNormal) {
3661                     if (oldi > next_out) {
3662                         SendToPlayer(&buf[next_out], oldi - next_out);
3663                         next_out = oldi;
3664                     }
3665                     Colorize(ColorNormal, FALSE);
3666                     curColor = ColorNormal;
3667                 }
3668                 if (started == STARTED_BOARD) {
3669                     started = STARTED_NONE;
3670                     parse[parse_pos] = NULLCHAR;
3671                     ParseBoard12(parse);
3672                     ics_user_moved = 0;
3673
3674                     /* Send premove here */
3675                     if (appData.premove) {
3676                       char str[MSG_SIZ];
3677                       if (currentMove == 0 &&
3678                           gameMode == IcsPlayingWhite &&
3679                           appData.premoveWhite) {
3680                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3681                         if (appData.debugMode)
3682                           fprintf(debugFP, "Sending premove:\n");
3683                         SendToICS(str);
3684                       } else if (currentMove == 1 &&
3685                                  gameMode == IcsPlayingBlack &&
3686                                  appData.premoveBlack) {
3687                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3688                         if (appData.debugMode)
3689                           fprintf(debugFP, "Sending premove:\n");
3690                         SendToICS(str);
3691                       } else if (gotPremove) {
3692                         gotPremove = 0;
3693                         ClearPremoveHighlights();
3694                         if (appData.debugMode)
3695                           fprintf(debugFP, "Sending premove:\n");
3696                           UserMoveEvent(premoveFromX, premoveFromY,
3697                                         premoveToX, premoveToY,
3698                                         premovePromoChar);
3699                       }
3700                     }
3701
3702                     /* Usually suppress following prompt */
3703                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3704                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3705                         if (looking_at(buf, &i, "*% ")) {
3706                             savingComment = FALSE;
3707                             suppressKibitz = 0;
3708                         }
3709                     }
3710                     next_out = i;
3711                 } else if (started == STARTED_HOLDINGS) {
3712                     int gamenum;
3713                     char new_piece[MSG_SIZ];
3714                     started = STARTED_NONE;
3715                     parse[parse_pos] = NULLCHAR;
3716                     if (appData.debugMode)
3717                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3718                                                         parse, currentMove);
3719                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3720                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3721                         if (gameInfo.variant == VariantNormal) {
3722                           /* [HGM] We seem to switch variant during a game!
3723                            * Presumably no holdings were displayed, so we have
3724                            * to move the position two files to the right to
3725                            * create room for them!
3726                            */
3727                           VariantClass newVariant;
3728                           switch(gameInfo.boardWidth) { // base guess on board width
3729                                 case 9:  newVariant = VariantShogi; break;
3730                                 case 10: newVariant = VariantGreat; break;
3731                                 default: newVariant = VariantCrazyhouse; break;
3732                           }
3733                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3734                           /* Get a move list just to see the header, which
3735                              will tell us whether this is really bug or zh */
3736                           if (ics_getting_history == H_FALSE) {
3737                             ics_getting_history = H_REQUESTED;
3738                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3739                             SendToICS(str);
3740                           }
3741                         }
3742                         new_piece[0] = NULLCHAR;
3743                         sscanf(parse, "game %d white [%s black [%s <- %s",
3744                                &gamenum, white_holding, black_holding,
3745                                new_piece);
3746                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3747                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3748                         /* [HGM] copy holdings to board holdings area */
3749                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3750                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3751                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3752 #if ZIPPY
3753                         if (appData.zippyPlay && first.initDone) {
3754                             ZippyHoldings(white_holding, black_holding,
3755                                           new_piece);
3756                         }
3757 #endif /*ZIPPY*/
3758                         if (tinyLayout || smallLayout) {
3759                             char wh[16], bh[16];
3760                             PackHolding(wh, white_holding);
3761                             PackHolding(bh, black_holding);
3762                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3763                                     gameInfo.white, gameInfo.black);
3764                         } else {
3765                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3766                                     gameInfo.white, white_holding,
3767                                     gameInfo.black, black_holding);
3768                         }
3769                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3770                         DrawPosition(FALSE, boards[currentMove]);
3771                         DisplayTitle(str);
3772                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3773                         sscanf(parse, "game %d white [%s black [%s <- %s",
3774                                &gamenum, white_holding, black_holding,
3775                                new_piece);
3776                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3777                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3778                         /* [HGM] copy holdings to partner-board holdings area */
3779                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3780                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3781                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3782                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3783                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3784                       }
3785                     }
3786                     /* Suppress following prompt */
3787                     if (looking_at(buf, &i, "*% ")) {
3788                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3789                         savingComment = FALSE;
3790                         suppressKibitz = 0;
3791                     }
3792                     next_out = i;
3793                 }
3794                 continue;
3795             }
3796
3797             i++;                /* skip unparsed character and loop back */
3798         }
3799
3800         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3801 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3802 //          SendToPlayer(&buf[next_out], i - next_out);
3803             started != STARTED_HOLDINGS && leftover_start > next_out) {
3804             SendToPlayer(&buf[next_out], leftover_start - next_out);
3805             next_out = i;
3806         }
3807
3808         leftover_len = buf_len - leftover_start;
3809         /* if buffer ends with something we couldn't parse,
3810            reparse it after appending the next read */
3811
3812     } else if (count == 0) {
3813         RemoveInputSource(isr);
3814         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3815     } else {
3816         DisplayFatalError(_("Error reading from ICS"), error, 1);
3817     }
3818 }
3819
3820
3821 /* Board style 12 looks like this:
3822
3823    <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
3824
3825  * The "<12> " is stripped before it gets to this routine.  The two
3826  * trailing 0's (flip state and clock ticking) are later addition, and
3827  * some chess servers may not have them, or may have only the first.
3828  * Additional trailing fields may be added in the future.
3829  */
3830
3831 #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"
3832
3833 #define RELATION_OBSERVING_PLAYED    0
3834 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3835 #define RELATION_PLAYING_MYMOVE      1
3836 #define RELATION_PLAYING_NOTMYMOVE  -1
3837 #define RELATION_EXAMINING           2
3838 #define RELATION_ISOLATED_BOARD     -3
3839 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3840
3841 void
3842 ParseBoard12(string)
3843      char *string;
3844 {
3845     GameMode newGameMode;
3846     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3847     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3848     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3849     char to_play, board_chars[200];
3850     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3851     char black[32], white[32];
3852     Board board;
3853     int prevMove = currentMove;
3854     int ticking = 2;
3855     ChessMove moveType;
3856     int fromX, fromY, toX, toY;
3857     char promoChar;
3858     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3859     char *bookHit = NULL; // [HGM] book
3860     Boolean weird = FALSE, reqFlag = FALSE;
3861
3862     fromX = fromY = toX = toY = -1;
3863
3864     newGame = FALSE;
3865
3866     if (appData.debugMode)
3867       fprintf(debugFP, _("Parsing board: %s\n"), string);
3868
3869     move_str[0] = NULLCHAR;
3870     elapsed_time[0] = NULLCHAR;
3871     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3872         int  i = 0, j;
3873         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3874             if(string[i] == ' ') { ranks++; files = 0; }
3875             else files++;
3876             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3877             i++;
3878         }
3879         for(j = 0; j <i; j++) board_chars[j] = string[j];
3880         board_chars[i] = '\0';
3881         string += i + 1;
3882     }
3883     n = sscanf(string, PATTERN, &to_play, &double_push,
3884                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3885                &gamenum, white, black, &relation, &basetime, &increment,
3886                &white_stren, &black_stren, &white_time, &black_time,
3887                &moveNum, str, elapsed_time, move_str, &ics_flip,
3888                &ticking);
3889
3890     if (n < 21) {
3891         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3892         DisplayError(str, 0);
3893         return;
3894     }
3895
3896     /* Convert the move number to internal form */
3897     moveNum = (moveNum - 1) * 2;
3898     if (to_play == 'B') moveNum++;
3899     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3900       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3901                         0, 1);
3902       return;
3903     }
3904
3905     switch (relation) {
3906       case RELATION_OBSERVING_PLAYED:
3907       case RELATION_OBSERVING_STATIC:
3908         if (gamenum == -1) {
3909             /* Old ICC buglet */
3910             relation = RELATION_OBSERVING_STATIC;
3911         }
3912         newGameMode = IcsObserving;
3913         break;
3914       case RELATION_PLAYING_MYMOVE:
3915       case RELATION_PLAYING_NOTMYMOVE:
3916         newGameMode =
3917           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3918             IcsPlayingWhite : IcsPlayingBlack;
3919         break;
3920       case RELATION_EXAMINING:
3921         newGameMode = IcsExamining;
3922         break;
3923       case RELATION_ISOLATED_BOARD:
3924       default:
3925         /* Just display this board.  If user was doing something else,
3926            we will forget about it until the next board comes. */
3927         newGameMode = IcsIdle;
3928         break;
3929       case RELATION_STARTING_POSITION:
3930         newGameMode = gameMode;
3931         break;
3932     }
3933
3934     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3935          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3936       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3937       char *toSqr;
3938       for (k = 0; k < ranks; k++) {
3939         for (j = 0; j < files; j++)
3940           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3941         if(gameInfo.holdingsWidth > 1) {
3942              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3943              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3944         }
3945       }
3946       CopyBoard(partnerBoard, board);
3947       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3948         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3949         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3950       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3951       if(toSqr = strchr(str, '-')) {
3952         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3953         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3954       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3955       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3956       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3957       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3958       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3959       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3960                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3961       DisplayMessage(partnerStatus, "");
3962         partnerBoardValid = TRUE;
3963       return;
3964     }
3965
3966     /* Modify behavior for initial board display on move listing
3967        of wild games.
3968        */
3969     switch (ics_getting_history) {
3970       case H_FALSE:
3971       case H_REQUESTED:
3972         break;
3973       case H_GOT_REQ_HEADER:
3974       case H_GOT_UNREQ_HEADER:
3975         /* This is the initial position of the current game */
3976         gamenum = ics_gamenum;
3977         moveNum = 0;            /* old ICS bug workaround */
3978         if (to_play == 'B') {
3979           startedFromSetupPosition = TRUE;
3980           blackPlaysFirst = TRUE;
3981           moveNum = 1;
3982           if (forwardMostMove == 0) forwardMostMove = 1;
3983           if (backwardMostMove == 0) backwardMostMove = 1;
3984           if (currentMove == 0) currentMove = 1;
3985         }
3986         newGameMode = gameMode;
3987         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3988         break;
3989       case H_GOT_UNWANTED_HEADER:
3990         /* This is an initial board that we don't want */
3991         return;
3992       case H_GETTING_MOVES:
3993         /* Should not happen */
3994         DisplayError(_("Error gathering move list: extra board"), 0);
3995         ics_getting_history = H_FALSE;
3996         return;
3997     }
3998
3999    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4000                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4001      /* [HGM] We seem to have switched variant unexpectedly
4002       * Try to guess new variant from board size
4003       */
4004           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4005           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4006           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4007           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4008           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4009           if(!weird) newVariant = VariantNormal;
4010           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4011           /* Get a move list just to see the header, which
4012              will tell us whether this is really bug or zh */
4013           if (ics_getting_history == H_FALSE) {
4014             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4015             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4016             SendToICS(str);
4017           }
4018     }
4019
4020     /* Take action if this is the first board of a new game, or of a
4021        different game than is currently being displayed.  */
4022     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4023         relation == RELATION_ISOLATED_BOARD) {
4024
4025         /* Forget the old game and get the history (if any) of the new one */
4026         if (gameMode != BeginningOfGame) {
4027           Reset(TRUE, TRUE);
4028         }
4029         newGame = TRUE;
4030         if (appData.autoRaiseBoard) BoardToTop();
4031         prevMove = -3;
4032         if (gamenum == -1) {
4033             newGameMode = IcsIdle;
4034         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4035                    appData.getMoveList && !reqFlag) {
4036             /* Need to get game history */
4037             ics_getting_history = H_REQUESTED;
4038             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4039             SendToICS(str);
4040         }
4041
4042         /* Initially flip the board to have black on the bottom if playing
4043            black or if the ICS flip flag is set, but let the user change
4044            it with the Flip View button. */
4045         flipView = appData.autoFlipView ?
4046           (newGameMode == IcsPlayingBlack) || ics_flip :
4047           appData.flipView;
4048
4049         /* Done with values from previous mode; copy in new ones */
4050         gameMode = newGameMode;
4051         ModeHighlight();
4052         ics_gamenum = gamenum;
4053         if (gamenum == gs_gamenum) {
4054             int klen = strlen(gs_kind);
4055             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4056             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4057             gameInfo.event = StrSave(str);
4058         } else {
4059             gameInfo.event = StrSave("ICS game");
4060         }
4061         gameInfo.site = StrSave(appData.icsHost);
4062         gameInfo.date = PGNDate();
4063         gameInfo.round = StrSave("-");
4064         gameInfo.white = StrSave(white);
4065         gameInfo.black = StrSave(black);
4066         timeControl = basetime * 60 * 1000;
4067         timeControl_2 = 0;
4068         timeIncrement = increment * 1000;
4069         movesPerSession = 0;
4070         gameInfo.timeControl = TimeControlTagValue();
4071         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4072   if (appData.debugMode) {
4073     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4074     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4075     setbuf(debugFP, NULL);
4076   }
4077
4078         gameInfo.outOfBook = NULL;
4079
4080         /* Do we have the ratings? */
4081         if (strcmp(player1Name, white) == 0 &&
4082             strcmp(player2Name, black) == 0) {
4083             if (appData.debugMode)
4084               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4085                       player1Rating, player2Rating);
4086             gameInfo.whiteRating = player1Rating;
4087             gameInfo.blackRating = player2Rating;
4088         } else if (strcmp(player2Name, white) == 0 &&
4089                    strcmp(player1Name, black) == 0) {
4090             if (appData.debugMode)
4091               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4092                       player2Rating, player1Rating);
4093             gameInfo.whiteRating = player2Rating;
4094             gameInfo.blackRating = player1Rating;
4095         }
4096         player1Name[0] = player2Name[0] = NULLCHAR;
4097
4098         /* Silence shouts if requested */
4099         if (appData.quietPlay &&
4100             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4101             SendToICS(ics_prefix);
4102             SendToICS("set shout 0\n");
4103         }
4104     }
4105
4106     /* Deal with midgame name changes */
4107     if (!newGame) {
4108         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4109             if (gameInfo.white) free(gameInfo.white);
4110             gameInfo.white = StrSave(white);
4111         }
4112         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4113             if (gameInfo.black) free(gameInfo.black);
4114             gameInfo.black = StrSave(black);
4115         }
4116     }
4117
4118     /* Throw away game result if anything actually changes in examine mode */
4119     if (gameMode == IcsExamining && !newGame) {
4120         gameInfo.result = GameUnfinished;
4121         if (gameInfo.resultDetails != NULL) {
4122             free(gameInfo.resultDetails);
4123             gameInfo.resultDetails = NULL;
4124         }
4125     }
4126
4127     /* In pausing && IcsExamining mode, we ignore boards coming
4128        in if they are in a different variation than we are. */
4129     if (pauseExamInvalid) return;
4130     if (pausing && gameMode == IcsExamining) {
4131         if (moveNum <= pauseExamForwardMostMove) {
4132             pauseExamInvalid = TRUE;
4133             forwardMostMove = pauseExamForwardMostMove;
4134             return;
4135         }
4136     }
4137
4138   if (appData.debugMode) {
4139     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4140   }
4141     /* Parse the board */
4142     for (k = 0; k < ranks; k++) {
4143       for (j = 0; j < files; j++)
4144         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4145       if(gameInfo.holdingsWidth > 1) {
4146            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4147            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4148       }
4149     }
4150     CopyBoard(boards[moveNum], board);
4151     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4152     if (moveNum == 0) {
4153         startedFromSetupPosition =
4154           !CompareBoards(board, initialPosition);
4155         if(startedFromSetupPosition)
4156             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4157     }
4158
4159     /* [HGM] Set castling rights. Take the outermost Rooks,
4160        to make it also work for FRC opening positions. Note that board12
4161        is really defective for later FRC positions, as it has no way to
4162        indicate which Rook can castle if they are on the same side of King.
4163        For the initial position we grant rights to the outermost Rooks,
4164        and remember thos rights, and we then copy them on positions
4165        later in an FRC game. This means WB might not recognize castlings with
4166        Rooks that have moved back to their original position as illegal,
4167        but in ICS mode that is not its job anyway.
4168     */
4169     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4170     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4171
4172         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4173             if(board[0][i] == WhiteRook) j = i;
4174         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4175         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4176             if(board[0][i] == WhiteRook) j = i;
4177         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4178         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4179             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4180         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4181         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4182             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4183         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4184
4185         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4186         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4188         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4189             if(board[BOARD_HEIGHT-1][k] == bKing)
4190                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4191         if(gameInfo.variant == VariantTwoKings) {
4192             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4193             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4194             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4195         }
4196     } else { int r;
4197         r = boards[moveNum][CASTLING][0] = initialRights[0];
4198         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4199         r = boards[moveNum][CASTLING][1] = initialRights[1];
4200         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4201         r = boards[moveNum][CASTLING][3] = initialRights[3];
4202         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4203         r = boards[moveNum][CASTLING][4] = initialRights[4];
4204         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4205         /* wildcastle kludge: always assume King has rights */
4206         r = boards[moveNum][CASTLING][2] = initialRights[2];
4207         r = boards[moveNum][CASTLING][5] = initialRights[5];
4208     }
4209     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4210     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4211
4212
4213     if (ics_getting_history == H_GOT_REQ_HEADER ||
4214         ics_getting_history == H_GOT_UNREQ_HEADER) {
4215         /* This was an initial position from a move list, not
4216            the current position */
4217         return;
4218     }
4219
4220     /* Update currentMove and known move number limits */
4221     newMove = newGame || moveNum > forwardMostMove;
4222
4223     if (newGame) {
4224         forwardMostMove = backwardMostMove = currentMove = moveNum;
4225         if (gameMode == IcsExamining && moveNum == 0) {
4226           /* Workaround for ICS limitation: we are not told the wild
4227              type when starting to examine a game.  But if we ask for
4228              the move list, the move list header will tell us */
4229             ics_getting_history = H_REQUESTED;
4230             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4231             SendToICS(str);
4232         }
4233     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4234                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4235 #if ZIPPY
4236         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4237         /* [HGM] applied this also to an engine that is silently watching        */
4238         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4239             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4240             gameInfo.variant == currentlyInitializedVariant) {
4241           takeback = forwardMostMove - moveNum;
4242           for (i = 0; i < takeback; i++) {
4243             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4244             SendToProgram("undo\n", &first);
4245           }
4246         }
4247 #endif
4248
4249         forwardMostMove = moveNum;
4250         if (!pausing || currentMove > forwardMostMove)
4251           currentMove = forwardMostMove;
4252     } else {
4253         /* New part of history that is not contiguous with old part */
4254         if (pausing && gameMode == IcsExamining) {
4255             pauseExamInvalid = TRUE;
4256             forwardMostMove = pauseExamForwardMostMove;
4257             return;
4258         }
4259         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4260 #if ZIPPY
4261             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4262                 // [HGM] when we will receive the move list we now request, it will be
4263                 // fed to the engine from the first move on. So if the engine is not
4264                 // in the initial position now, bring it there.
4265                 InitChessProgram(&first, 0);
4266             }
4267 #endif
4268             ics_getting_history = H_REQUESTED;
4269             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4270             SendToICS(str);
4271         }
4272         forwardMostMove = backwardMostMove = currentMove = moveNum;
4273     }
4274
4275     /* Update the clocks */
4276     if (strchr(elapsed_time, '.')) {
4277       /* Time is in ms */
4278       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4279       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4280     } else {
4281       /* Time is in seconds */
4282       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4283       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4284     }
4285
4286
4287 #if ZIPPY
4288     if (appData.zippyPlay && newGame &&
4289         gameMode != IcsObserving && gameMode != IcsIdle &&
4290         gameMode != IcsExamining)
4291       ZippyFirstBoard(moveNum, basetime, increment);
4292 #endif
4293
4294     /* Put the move on the move list, first converting
4295        to canonical algebraic form. */
4296     if (moveNum > 0) {
4297   if (appData.debugMode) {
4298     if (appData.debugMode) { int f = forwardMostMove;
4299         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4300                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4301                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4302     }
4303     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4304     fprintf(debugFP, "moveNum = %d\n", moveNum);
4305     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4306     setbuf(debugFP, NULL);
4307   }
4308         if (moveNum <= backwardMostMove) {
4309             /* We don't know what the board looked like before
4310                this move.  Punt. */
4311           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4312             strcat(parseList[moveNum - 1], " ");
4313             strcat(parseList[moveNum - 1], elapsed_time);
4314             moveList[moveNum - 1][0] = NULLCHAR;
4315         } else if (strcmp(move_str, "none") == 0) {
4316             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4317             /* Again, we don't know what the board looked like;
4318                this is really the start of the game. */
4319             parseList[moveNum - 1][0] = NULLCHAR;
4320             moveList[moveNum - 1][0] = NULLCHAR;
4321             backwardMostMove = moveNum;
4322             startedFromSetupPosition = TRUE;
4323             fromX = fromY = toX = toY = -1;
4324         } else {
4325           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4326           //                 So we parse the long-algebraic move string in stead of the SAN move
4327           int valid; char buf[MSG_SIZ], *prom;
4328
4329           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4330                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4331           // str looks something like "Q/a1-a2"; kill the slash
4332           if(str[1] == '/')
4333             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4334           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4335           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4336                 strcat(buf, prom); // long move lacks promo specification!
4337           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4338                 if(appData.debugMode)
4339                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4340                 safeStrCpy(move_str, buf, MSG_SIZ);
4341           }
4342           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4343                                 &fromX, &fromY, &toX, &toY, &promoChar)
4344                || ParseOneMove(buf, moveNum - 1, &moveType,
4345                                 &fromX, &fromY, &toX, &toY, &promoChar);
4346           // end of long SAN patch
4347           if (valid) {
4348             (void) CoordsToAlgebraic(boards[moveNum - 1],
4349                                      PosFlags(moveNum - 1),
4350                                      fromY, fromX, toY, toX, promoChar,
4351                                      parseList[moveNum-1]);
4352             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4353               case MT_NONE:
4354               case MT_STALEMATE:
4355               default:
4356                 break;
4357               case MT_CHECK:
4358                 if(gameInfo.variant != VariantShogi)
4359                     strcat(parseList[moveNum - 1], "+");
4360                 break;
4361               case MT_CHECKMATE:
4362               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4363                 strcat(parseList[moveNum - 1], "#");
4364                 break;
4365             }
4366             strcat(parseList[moveNum - 1], " ");
4367             strcat(parseList[moveNum - 1], elapsed_time);
4368             /* currentMoveString is set as a side-effect of ParseOneMove */
4369             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4370             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4371             strcat(moveList[moveNum - 1], "\n");
4372
4373             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4374                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4375               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4376                 ChessSquare old, new = boards[moveNum][k][j];
4377                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4378                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4379                   if(old == new) continue;
4380                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4381                   else if(new == WhiteWazir || new == BlackWazir) {
4382                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4383                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4384                       else boards[moveNum][k][j] = old; // preserve type of Gold
4385                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4386                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4387               }
4388           } else {
4389             /* Move from ICS was illegal!?  Punt. */
4390             if (appData.debugMode) {
4391               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4392               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4393             }
4394             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4395             strcat(parseList[moveNum - 1], " ");
4396             strcat(parseList[moveNum - 1], elapsed_time);
4397             moveList[moveNum - 1][0] = NULLCHAR;
4398             fromX = fromY = toX = toY = -1;
4399           }
4400         }
4401   if (appData.debugMode) {
4402     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4403     setbuf(debugFP, NULL);
4404   }
4405
4406 #if ZIPPY
4407         /* Send move to chess program (BEFORE animating it). */
4408         if (appData.zippyPlay && !newGame && newMove &&
4409            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4410
4411             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4412                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4413                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4414                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4415                             move_str);
4416                     DisplayError(str, 0);
4417                 } else {
4418                     if (first.sendTime) {
4419                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4420                     }
4421                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4422                     if (firstMove && !bookHit) {
4423                         firstMove = FALSE;
4424                         if (first.useColors) {
4425                           SendToProgram(gameMode == IcsPlayingWhite ?
4426                                         "white\ngo\n" :
4427                                         "black\ngo\n", &first);
4428                         } else {
4429                           SendToProgram("go\n", &first);
4430                         }
4431                         first.maybeThinking = TRUE;
4432                     }
4433                 }
4434             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4435               if (moveList[moveNum - 1][0] == NULLCHAR) {
4436                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4437                 DisplayError(str, 0);
4438               } else {
4439                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4440                 SendMoveToProgram(moveNum - 1, &first);
4441               }
4442             }
4443         }
4444 #endif
4445     }
4446
4447     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4448         /* If move comes from a remote source, animate it.  If it
4449            isn't remote, it will have already been animated. */
4450         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4451             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4452         }
4453         if (!pausing && appData.highlightLastMove) {
4454             SetHighlights(fromX, fromY, toX, toY);
4455         }
4456     }
4457
4458     /* Start the clocks */
4459     whiteFlag = blackFlag = FALSE;
4460     appData.clockMode = !(basetime == 0 && increment == 0);
4461     if (ticking == 0) {
4462       ics_clock_paused = TRUE;
4463       StopClocks();
4464     } else if (ticking == 1) {
4465       ics_clock_paused = FALSE;
4466     }
4467     if (gameMode == IcsIdle ||
4468         relation == RELATION_OBSERVING_STATIC ||
4469         relation == RELATION_EXAMINING ||
4470         ics_clock_paused)
4471       DisplayBothClocks();
4472     else
4473       StartClocks();
4474
4475     /* Display opponents and material strengths */
4476     if (gameInfo.variant != VariantBughouse &&
4477         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4478         if (tinyLayout || smallLayout) {
4479             if(gameInfo.variant == VariantNormal)
4480               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4481                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4482                     basetime, increment);
4483             else
4484               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4485                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4486                     basetime, increment, (int) gameInfo.variant);
4487         } else {
4488             if(gameInfo.variant == VariantNormal)
4489               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4490                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4491                     basetime, increment);
4492             else
4493               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4494                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4495                     basetime, increment, VariantName(gameInfo.variant));
4496         }
4497         DisplayTitle(str);
4498   if (appData.debugMode) {
4499     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4500   }
4501     }
4502
4503
4504     /* Display the board */
4505     if (!pausing && !appData.noGUI) {
4506
4507       if (appData.premove)
4508           if (!gotPremove ||
4509              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4510              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4511               ClearPremoveHighlights();
4512
4513       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4514         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4515       DrawPosition(j, boards[currentMove]);
4516
4517       DisplayMove(moveNum - 1);
4518       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4519             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4520               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4521         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4522       }
4523     }
4524
4525     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4526 #if ZIPPY
4527     if(bookHit) { // [HGM] book: simulate book reply
4528         static char bookMove[MSG_SIZ]; // a bit generous?
4529
4530         programStats.nodes = programStats.depth = programStats.time =
4531         programStats.score = programStats.got_only_move = 0;
4532         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4533
4534         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4535         strcat(bookMove, bookHit);
4536         HandleMachineMove(bookMove, &first);
4537     }
4538 #endif
4539 }
4540
4541 void
4542 GetMoveListEvent()
4543 {
4544     char buf[MSG_SIZ];
4545     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4546         ics_getting_history = H_REQUESTED;
4547         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4548         SendToICS(buf);
4549     }
4550 }
4551
4552 void
4553 AnalysisPeriodicEvent(force)
4554      int force;
4555 {
4556     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4557          && !force) || !appData.periodicUpdates)
4558       return;
4559
4560     /* Send . command to Crafty to collect stats */
4561     SendToProgram(".\n", &first);
4562
4563     /* Don't send another until we get a response (this makes
4564        us stop sending to old Crafty's which don't understand
4565        the "." command (sending illegal cmds resets node count & time,
4566        which looks bad)) */
4567     programStats.ok_to_send = 0;
4568 }
4569
4570 void ics_update_width(new_width)
4571         int new_width;
4572 {
4573         ics_printf("set width %d\n", new_width);
4574 }
4575
4576 void
4577 SendMoveToProgram(moveNum, cps)
4578      int moveNum;
4579      ChessProgramState *cps;
4580 {
4581     char buf[MSG_SIZ];
4582
4583     if (cps->useUsermove) {
4584       SendToProgram("usermove ", cps);
4585     }
4586     if (cps->useSAN) {
4587       char *space;
4588       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4589         int len = space - parseList[moveNum];
4590         memcpy(buf, parseList[moveNum], len);
4591         buf[len++] = '\n';
4592         buf[len] = NULLCHAR;
4593       } else {
4594         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4595       }
4596       SendToProgram(buf, cps);
4597     } else {
4598       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4599         AlphaRank(moveList[moveNum], 4);
4600         SendToProgram(moveList[moveNum], cps);
4601         AlphaRank(moveList[moveNum], 4); // and back
4602       } else
4603       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4604        * the engine. It would be nice to have a better way to identify castle
4605        * moves here. */
4606       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4607                                                                          && cps->useOOCastle) {
4608         int fromX = moveList[moveNum][0] - AAA;
4609         int fromY = moveList[moveNum][1] - ONE;
4610         int toX = moveList[moveNum][2] - AAA;
4611         int toY = moveList[moveNum][3] - ONE;
4612         if((boards[moveNum][fromY][fromX] == WhiteKing
4613             && boards[moveNum][toY][toX] == WhiteRook)
4614            || (boards[moveNum][fromY][fromX] == BlackKing
4615                && boards[moveNum][toY][toX] == BlackRook)) {
4616           if(toX > fromX) SendToProgram("O-O\n", cps);
4617           else SendToProgram("O-O-O\n", cps);
4618         }
4619         else SendToProgram(moveList[moveNum], cps);
4620       }
4621       else SendToProgram(moveList[moveNum], cps);
4622       /* End of additions by Tord */
4623     }
4624
4625     /* [HGM] setting up the opening has brought engine in force mode! */
4626     /*       Send 'go' if we are in a mode where machine should play. */
4627     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4628         (gameMode == TwoMachinesPlay   ||
4629 #if ZIPPY
4630          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4631 #endif
4632          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4633         SendToProgram("go\n", cps);
4634   if (appData.debugMode) {
4635     fprintf(debugFP, "(extra)\n");
4636   }
4637     }
4638     setboardSpoiledMachineBlack = 0;
4639 }
4640
4641 void
4642 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4643      ChessMove moveType;
4644      int fromX, fromY, toX, toY;
4645      char promoChar;
4646 {
4647     char user_move[MSG_SIZ];
4648
4649     switch (moveType) {
4650       default:
4651         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4652                 (int)moveType, fromX, fromY, toX, toY);
4653         DisplayError(user_move + strlen("say "), 0);
4654         break;
4655       case WhiteKingSideCastle:
4656       case BlackKingSideCastle:
4657       case WhiteQueenSideCastleWild:
4658       case BlackQueenSideCastleWild:
4659       /* PUSH Fabien */
4660       case WhiteHSideCastleFR:
4661       case BlackHSideCastleFR:
4662       /* POP Fabien */
4663         snprintf(user_move, MSG_SIZ, "o-o\n");
4664         break;
4665       case WhiteQueenSideCastle:
4666       case BlackQueenSideCastle:
4667       case WhiteKingSideCastleWild:
4668       case BlackKingSideCastleWild:
4669       /* PUSH Fabien */
4670       case WhiteASideCastleFR:
4671       case BlackASideCastleFR:
4672       /* POP Fabien */
4673         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4674         break;
4675       case WhiteNonPromotion:
4676       case BlackNonPromotion:
4677         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4678         break;
4679       case WhitePromotion:
4680       case BlackPromotion:
4681         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4682           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4683                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4684                 PieceToChar(WhiteFerz));
4685         else if(gameInfo.variant == VariantGreat)
4686           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4687                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4688                 PieceToChar(WhiteMan));
4689         else
4690           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4691                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4692                 promoChar);
4693         break;
4694       case WhiteDrop:
4695       case BlackDrop:
4696       drop:
4697         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4698                  ToUpper(PieceToChar((ChessSquare) fromX)),
4699                  AAA + toX, ONE + toY);
4700         break;
4701       case IllegalMove:  /* could be a variant we don't quite understand */
4702         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4703       case NormalMove:
4704       case WhiteCapturesEnPassant:
4705       case BlackCapturesEnPassant:
4706         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4707                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4708         break;
4709     }
4710     SendToICS(user_move);
4711     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4712         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4713 }
4714
4715 void
4716 UploadGameEvent()
4717 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4718     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4719     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4720     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4721         DisplayError("You cannot do this while you are playing or observing", 0);
4722         return;
4723     }
4724     if(gameMode != IcsExamining) { // is this ever not the case?
4725         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4726
4727         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4728           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4729         } else { // on FICS we must first go to general examine mode
4730           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4731         }
4732         if(gameInfo.variant != VariantNormal) {
4733             // try figure out wild number, as xboard names are not always valid on ICS
4734             for(i=1; i<=36; i++) {
4735               snprintf(buf, MSG_SIZ, "wild/%d", i);
4736                 if(StringToVariant(buf) == gameInfo.variant) break;
4737             }
4738             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4739             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4740             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4741         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4742         SendToICS(ics_prefix);
4743         SendToICS(buf);
4744         if(startedFromSetupPosition || backwardMostMove != 0) {
4745           fen = PositionToFEN(backwardMostMove, NULL);
4746           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4747             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4748             SendToICS(buf);
4749           } else { // FICS: everything has to set by separate bsetup commands
4750             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4751             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4752             SendToICS(buf);
4753             if(!WhiteOnMove(backwardMostMove)) {
4754                 SendToICS("bsetup tomove black\n");
4755             }
4756             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4757             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4758             SendToICS(buf);
4759             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4760             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4761             SendToICS(buf);
4762             i = boards[backwardMostMove][EP_STATUS];
4763             if(i >= 0) { // set e.p.
4764               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4765                 SendToICS(buf);
4766             }
4767             bsetup++;
4768           }
4769         }
4770       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4771             SendToICS("bsetup done\n"); // switch to normal examining.
4772     }
4773     for(i = backwardMostMove; i<last; i++) {
4774         char buf[20];
4775         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4776         SendToICS(buf);
4777     }
4778     SendToICS(ics_prefix);
4779     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4780 }
4781
4782 void
4783 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4784      int rf, ff, rt, ft;
4785      char promoChar;
4786      char move[7];
4787 {
4788     if (rf == DROP_RANK) {
4789       sprintf(move, "%c@%c%c\n",
4790                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4791     } else {
4792         if (promoChar == 'x' || promoChar == NULLCHAR) {
4793           sprintf(move, "%c%c%c%c\n",
4794                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4795         } else {
4796             sprintf(move, "%c%c%c%c%c\n",
4797                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4798         }
4799     }
4800 }
4801
4802 void
4803 ProcessICSInitScript(f)
4804      FILE *f;
4805 {
4806     char buf[MSG_SIZ];
4807
4808     while (fgets(buf, MSG_SIZ, f)) {
4809         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4810     }
4811
4812     fclose(f);
4813 }
4814
4815
4816 void
4817 Sweep(int step)
4818 {
4819     ChessSquare piece = boards[currentMove][toY][toX];
4820     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4821     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4822     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4823     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4824     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4825     if(toY != BOARD_HEIGHT-1 && toY != 0) pawn = EmptySquare;
4826     do {
4827         promoSweep -= step;
4828         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4829         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4830         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4831         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4832         if(!step) step = 1;
4833     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4834             appData.testLegality && (promoSweep == king ||
4835             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4836     boards[currentMove][toY][toX] = promoSweep;
4837     DrawPosition(FALSE, boards[currentMove]);
4838     boards[currentMove][toY][toX] = piece;
4839 }
4840
4841 static int lastX, lastY;
4842
4843 void PromoScroll(int x, int y)
4844 {
4845   int step = 0;
4846   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
4847   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4848   if(!step) return;
4849   lastX = x; lastY = y;
4850
4851   if(promoSweep == EmptySquare) return;
4852   Sweep(step);
4853 }
4854
4855 void
4856 NextPiece(int step)
4857 {
4858     ChessSquare piece = boards[currentMove][toY][toX];
4859     do {
4860         pieceSweep -= step;
4861         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4862         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4863         if(!step) step = -1;
4864     } while(PieceToChar(pieceSweep) == '.');
4865     boards[currentMove][toY][toX] = pieceSweep;
4866     DrawPosition(FALSE, boards[currentMove]);
4867     boards[currentMove][toY][toX] = piece;
4868 }
4869 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4870 void
4871 AlphaRank(char *move, int n)
4872 {
4873 //    char *p = move, c; int x, y;
4874
4875     if (appData.debugMode) {
4876         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4877     }
4878
4879     if(move[1]=='*' &&
4880        move[2]>='0' && move[2]<='9' &&
4881        move[3]>='a' && move[3]<='x'    ) {
4882         move[1] = '@';
4883         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4884         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4885     } else
4886     if(move[0]>='0' && move[0]<='9' &&
4887        move[1]>='a' && move[1]<='x' &&
4888        move[2]>='0' && move[2]<='9' &&
4889        move[3]>='a' && move[3]<='x'    ) {
4890         /* input move, Shogi -> normal */
4891         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4892         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4893         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4894         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4895     } else
4896     if(move[1]=='@' &&
4897        move[3]>='0' && move[3]<='9' &&
4898        move[2]>='a' && move[2]<='x'    ) {
4899         move[1] = '*';
4900         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4901         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4902     } else
4903     if(
4904        move[0]>='a' && move[0]<='x' &&
4905        move[3]>='0' && move[3]<='9' &&
4906        move[2]>='a' && move[2]<='x'    ) {
4907          /* output move, normal -> Shogi */
4908         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4909         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4910         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4911         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4912         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4913     }
4914     if (appData.debugMode) {
4915         fprintf(debugFP, "   out = '%s'\n", move);
4916     }
4917 }
4918
4919 char yy_textstr[8000];
4920
4921 /* Parser for moves from gnuchess, ICS, or user typein box */
4922 Boolean
4923 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4924      char *move;
4925      int moveNum;
4926      ChessMove *moveType;
4927      int *fromX, *fromY, *toX, *toY;
4928      char *promoChar;
4929 {
4930     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4931
4932     switch (*moveType) {
4933       case WhitePromotion:
4934       case BlackPromotion:
4935       case WhiteNonPromotion:
4936       case BlackNonPromotion:
4937       case NormalMove:
4938       case WhiteCapturesEnPassant:
4939       case BlackCapturesEnPassant:
4940       case WhiteKingSideCastle:
4941       case WhiteQueenSideCastle:
4942       case BlackKingSideCastle:
4943       case BlackQueenSideCastle:
4944       case WhiteKingSideCastleWild:
4945       case WhiteQueenSideCastleWild:
4946       case BlackKingSideCastleWild:
4947       case BlackQueenSideCastleWild:
4948       /* Code added by Tord: */
4949       case WhiteHSideCastleFR:
4950       case WhiteASideCastleFR:
4951       case BlackHSideCastleFR:
4952       case BlackASideCastleFR:
4953       /* End of code added by Tord */
4954       case IllegalMove:         /* bug or odd chess variant */
4955         *fromX = currentMoveString[0] - AAA;
4956         *fromY = currentMoveString[1] - ONE;
4957         *toX = currentMoveString[2] - AAA;
4958         *toY = currentMoveString[3] - ONE;
4959         *promoChar = currentMoveString[4];
4960         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4961             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4962     if (appData.debugMode) {
4963         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4964     }
4965             *fromX = *fromY = *toX = *toY = 0;
4966             return FALSE;
4967         }
4968         if (appData.testLegality) {
4969           return (*moveType != IllegalMove);
4970         } else {
4971           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4972                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4973         }
4974
4975       case WhiteDrop:
4976       case BlackDrop:
4977         *fromX = *moveType == WhiteDrop ?
4978           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4979           (int) CharToPiece(ToLower(currentMoveString[0]));
4980         *fromY = DROP_RANK;
4981         *toX = currentMoveString[2] - AAA;
4982         *toY = currentMoveString[3] - ONE;
4983         *promoChar = NULLCHAR;
4984         return TRUE;
4985
4986       case AmbiguousMove:
4987       case ImpossibleMove:
4988       case EndOfFile:
4989       case ElapsedTime:
4990       case Comment:
4991       case PGNTag:
4992       case NAG:
4993       case WhiteWins:
4994       case BlackWins:
4995       case GameIsDrawn:
4996       default:
4997     if (appData.debugMode) {
4998         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4999     }
5000         /* bug? */
5001         *fromX = *fromY = *toX = *toY = 0;
5002         *promoChar = NULLCHAR;
5003         return FALSE;
5004     }
5005 }
5006
5007
5008 void
5009 ParsePV(char *pv, Boolean storeComments)
5010 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5011   int fromX, fromY, toX, toY; char promoChar;
5012   ChessMove moveType;
5013   Boolean valid;
5014   int nr = 0;
5015
5016   endPV = forwardMostMove;
5017   do {
5018     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5019     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5020     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5021 if(appData.debugMode){
5022 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);
5023 }
5024     if(!valid && nr == 0 &&
5025        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5026         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5027         // Hande case where played move is different from leading PV move
5028         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5029         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5030         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5031         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5032           endPV += 2; // if position different, keep this
5033           moveList[endPV-1][0] = fromX + AAA;
5034           moveList[endPV-1][1] = fromY + ONE;
5035           moveList[endPV-1][2] = toX + AAA;
5036           moveList[endPV-1][3] = toY + ONE;
5037           parseList[endPV-1][0] = NULLCHAR;
5038           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5039         }
5040       }
5041     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5042     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5043     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5044     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5045         valid++; // allow comments in PV
5046         continue;
5047     }
5048     nr++;
5049     if(endPV+1 > framePtr) break; // no space, truncate
5050     if(!valid) break;
5051     endPV++;
5052     CopyBoard(boards[endPV], boards[endPV-1]);
5053     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5054     moveList[endPV-1][0] = fromX + AAA;
5055     moveList[endPV-1][1] = fromY + ONE;
5056     moveList[endPV-1][2] = toX + AAA;
5057     moveList[endPV-1][3] = toY + ONE;
5058     moveList[endPV-1][4] = promoChar;
5059     moveList[endPV-1][5] = NULLCHAR;
5060     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5061     if(storeComments)
5062         CoordsToAlgebraic(boards[endPV - 1],
5063                              PosFlags(endPV - 1),
5064                              fromY, fromX, toY, toX, promoChar,
5065                              parseList[endPV - 1]);
5066     else
5067         parseList[endPV-1][0] = NULLCHAR;
5068   } while(valid);
5069   currentMove = endPV;
5070   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5071   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5072                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5073   DrawPosition(TRUE, boards[currentMove]);
5074 }
5075
5076 Boolean
5077 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5078 {
5079         int startPV;
5080         char *p;
5081
5082         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5083         lastX = x; lastY = y;
5084         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5085         startPV = index;
5086         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5087         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5088         index = startPV;
5089         do{ while(buf[index] && buf[index] != '\n') index++;
5090         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5091         buf[index] = 0;
5092         ParsePV(buf+startPV, FALSE);
5093         *start = startPV; *end = index-1;
5094         return TRUE;
5095 }
5096
5097 Boolean
5098 LoadPV(int x, int y)
5099 { // called on right mouse click to load PV
5100   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5101   lastX = x; lastY = y;
5102   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5103   return TRUE;
5104 }
5105
5106 void
5107 UnLoadPV()
5108 {
5109   if(endPV < 0) return;
5110   endPV = -1;
5111   currentMove = forwardMostMove;
5112   ClearPremoveHighlights();
5113   DrawPosition(TRUE, boards[currentMove]);
5114 }
5115
5116 void
5117 MovePV(int x, int y, int h)
5118 { // step through PV based on mouse coordinates (called on mouse move)
5119   int margin = h>>3, step = 0, dist;
5120
5121   // we must somehow check if right button is still down (might be released off board!)
5122   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5123   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5124   if(!step) return;
5125   lastX = x; lastY = y;
5126
5127   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5128   if(endPV < 0) return;
5129   if(y < margin) step = 1; else
5130   if(y > h - margin) step = -1;
5131   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5132   currentMove += step;
5133   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5134   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5135                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5136   DrawPosition(FALSE, boards[currentMove]);
5137 }
5138
5139
5140 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5141 // All positions will have equal probability, but the current method will not provide a unique
5142 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5143 #define DARK 1
5144 #define LITE 2
5145 #define ANY 3
5146
5147 int squaresLeft[4];
5148 int piecesLeft[(int)BlackPawn];
5149 int seed, nrOfShuffles;
5150
5151 void GetPositionNumber()
5152 {       // sets global variable seed
5153         int i;
5154
5155         seed = appData.defaultFrcPosition;
5156         if(seed < 0) { // randomize based on time for negative FRC position numbers
5157                 for(i=0; i<50; i++) seed += random();
5158                 seed = random() ^ random() >> 8 ^ random() << 8;
5159                 if(seed<0) seed = -seed;
5160         }
5161 }
5162
5163 int put(Board board, int pieceType, int rank, int n, int shade)
5164 // put the piece on the (n-1)-th empty squares of the given shade
5165 {
5166         int i;
5167
5168         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5169                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5170                         board[rank][i] = (ChessSquare) pieceType;
5171                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5172                         squaresLeft[ANY]--;
5173                         piecesLeft[pieceType]--;
5174                         return i;
5175                 }
5176         }
5177         return -1;
5178 }
5179
5180
5181 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5182 // calculate where the next piece goes, (any empty square), and put it there
5183 {
5184         int i;
5185
5186         i = seed % squaresLeft[shade];
5187         nrOfShuffles *= squaresLeft[shade];
5188         seed /= squaresLeft[shade];
5189         put(board, pieceType, rank, i, shade);
5190 }
5191
5192 void AddTwoPieces(Board board, int pieceType, int rank)
5193 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5194 {
5195         int i, n=squaresLeft[ANY], j=n-1, k;
5196
5197         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5198         i = seed % k;  // pick one
5199         nrOfShuffles *= k;
5200         seed /= k;
5201         while(i >= j) i -= j--;
5202         j = n - 1 - j; i += j;
5203         put(board, pieceType, rank, j, ANY);
5204         put(board, pieceType, rank, i, ANY);
5205 }
5206
5207 void SetUpShuffle(Board board, int number)
5208 {
5209         int i, p, first=1;
5210
5211         GetPositionNumber(); nrOfShuffles = 1;
5212
5213         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5214         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5215         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5216
5217         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5218
5219         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5220             p = (int) board[0][i];
5221             if(p < (int) BlackPawn) piecesLeft[p] ++;
5222             board[0][i] = EmptySquare;
5223         }
5224
5225         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5226             // shuffles restricted to allow normal castling put KRR first
5227             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5228                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5229             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5230                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5231             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5232                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5233             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5234                 put(board, WhiteRook, 0, 0, ANY);
5235             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5236         }
5237
5238         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5239             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5240             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5241                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5242                 while(piecesLeft[p] >= 2) {
5243                     AddOnePiece(board, p, 0, LITE);
5244                     AddOnePiece(board, p, 0, DARK);
5245                 }
5246                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5247             }
5248
5249         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5250             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5251             // but we leave King and Rooks for last, to possibly obey FRC restriction
5252             if(p == (int)WhiteRook) continue;
5253             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5254             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5255         }
5256
5257         // now everything is placed, except perhaps King (Unicorn) and Rooks
5258
5259         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5260             // Last King gets castling rights
5261             while(piecesLeft[(int)WhiteUnicorn]) {
5262                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5263                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5264             }
5265
5266             while(piecesLeft[(int)WhiteKing]) {
5267                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5268                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5269             }
5270
5271
5272         } else {
5273             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5274             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5275         }
5276
5277         // Only Rooks can be left; simply place them all
5278         while(piecesLeft[(int)WhiteRook]) {
5279                 i = put(board, WhiteRook, 0, 0, ANY);
5280                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5281                         if(first) {
5282                                 first=0;
5283                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5284                         }
5285                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5286                 }
5287         }
5288         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5289             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5290         }
5291
5292         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5293 }
5294
5295 int SetCharTable( char *table, const char * map )
5296 /* [HGM] moved here from winboard.c because of its general usefulness */
5297 /*       Basically a safe strcpy that uses the last character as King */
5298 {
5299     int result = FALSE; int NrPieces;
5300
5301     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5302                     && NrPieces >= 12 && !(NrPieces&1)) {
5303         int i; /* [HGM] Accept even length from 12 to 34 */
5304
5305         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5306         for( i=0; i<NrPieces/2-1; i++ ) {
5307             table[i] = map[i];
5308             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5309         }
5310         table[(int) WhiteKing]  = map[NrPieces/2-1];
5311         table[(int) BlackKing]  = map[NrPieces-1];
5312
5313         result = TRUE;
5314     }
5315
5316     return result;
5317 }
5318
5319 void Prelude(Board board)
5320 {       // [HGM] superchess: random selection of exo-pieces
5321         int i, j, k; ChessSquare p;
5322         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5323
5324         GetPositionNumber(); // use FRC position number
5325
5326         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5327             SetCharTable(pieceToChar, appData.pieceToCharTable);
5328             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5329                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5330         }
5331
5332         j = seed%4;                 seed /= 4;
5333         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5334         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5335         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5336         j = seed%3 + (seed%3 >= j); seed /= 3;
5337         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5338         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5339         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5340         j = seed%3;                 seed /= 3;
5341         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5342         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5343         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5344         j = seed%2 + (seed%2 >= j); seed /= 2;
5345         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5346         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5347         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5348         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5349         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5350         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5351         put(board, exoPieces[0],    0, 0, ANY);
5352         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5353 }
5354
5355 void
5356 InitPosition(redraw)
5357      int redraw;
5358 {
5359     ChessSquare (* pieces)[BOARD_FILES];
5360     int i, j, pawnRow, overrule,
5361     oldx = gameInfo.boardWidth,
5362     oldy = gameInfo.boardHeight,
5363     oldh = gameInfo.holdingsWidth;
5364     static int oldv;
5365
5366     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5367
5368     /* [AS] Initialize pv info list [HGM] and game status */
5369     {
5370         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5371             pvInfoList[i].depth = 0;
5372             boards[i][EP_STATUS] = EP_NONE;
5373             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5374         }
5375
5376         initialRulePlies = 0; /* 50-move counter start */
5377
5378         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5379         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5380     }
5381
5382
5383     /* [HGM] logic here is completely changed. In stead of full positions */
5384     /* the initialized data only consist of the two backranks. The switch */
5385     /* selects which one we will use, which is than copied to the Board   */
5386     /* initialPosition, which for the rest is initialized by Pawns and    */
5387     /* empty squares. This initial position is then copied to boards[0],  */
5388     /* possibly after shuffling, so that it remains available.            */
5389
5390     gameInfo.holdingsWidth = 0; /* default board sizes */
5391     gameInfo.boardWidth    = 8;
5392     gameInfo.boardHeight   = 8;
5393     gameInfo.holdingsSize  = 0;
5394     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5395     for(i=0; i<BOARD_FILES-2; i++)
5396       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5397     initialPosition[EP_STATUS] = EP_NONE;
5398     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5399     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5400          SetCharTable(pieceNickName, appData.pieceNickNames);
5401     else SetCharTable(pieceNickName, "............");
5402     pieces = FIDEArray;
5403
5404     switch (gameInfo.variant) {
5405     case VariantFischeRandom:
5406       shuffleOpenings = TRUE;
5407     default:
5408       break;
5409     case VariantShatranj:
5410       pieces = ShatranjArray;
5411       nrCastlingRights = 0;
5412       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5413       break;
5414     case VariantMakruk:
5415       pieces = makrukArray;
5416       nrCastlingRights = 0;
5417       startedFromSetupPosition = TRUE;
5418       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5419       break;
5420     case VariantTwoKings:
5421       pieces = twoKingsArray;
5422       break;
5423     case VariantCapaRandom:
5424       shuffleOpenings = TRUE;
5425     case VariantCapablanca:
5426       pieces = CapablancaArray;
5427       gameInfo.boardWidth = 10;
5428       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5429       break;
5430     case VariantGothic:
5431       pieces = GothicArray;
5432       gameInfo.boardWidth = 10;
5433       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5434       break;
5435     case VariantSChess:
5436       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5437       gameInfo.holdingsSize = 7;
5438       break;
5439     case VariantJanus:
5440       pieces = JanusArray;
5441       gameInfo.boardWidth = 10;
5442       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5443       nrCastlingRights = 6;
5444         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5445         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5446         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5447         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5448         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5449         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5450       break;
5451     case VariantFalcon:
5452       pieces = FalconArray;
5453       gameInfo.boardWidth = 10;
5454       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5455       break;
5456     case VariantXiangqi:
5457       pieces = XiangqiArray;
5458       gameInfo.boardWidth  = 9;
5459       gameInfo.boardHeight = 10;
5460       nrCastlingRights = 0;
5461       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5462       break;
5463     case VariantShogi:
5464       pieces = ShogiArray;
5465       gameInfo.boardWidth  = 9;
5466       gameInfo.boardHeight = 9;
5467       gameInfo.holdingsSize = 7;
5468       nrCastlingRights = 0;
5469       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5470       break;
5471     case VariantCourier:
5472       pieces = CourierArray;
5473       gameInfo.boardWidth  = 12;
5474       nrCastlingRights = 0;
5475       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5476       break;
5477     case VariantKnightmate:
5478       pieces = KnightmateArray;
5479       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5480       break;
5481     case VariantSpartan:
5482       pieces = SpartanArray;
5483       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5484       break;
5485     case VariantFairy:
5486       pieces = fairyArray;
5487       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5488       break;
5489     case VariantGreat:
5490       pieces = GreatArray;
5491       gameInfo.boardWidth = 10;
5492       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5493       gameInfo.holdingsSize = 8;
5494       break;
5495     case VariantSuper:
5496       pieces = FIDEArray;
5497       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5498       gameInfo.holdingsSize = 8;
5499       startedFromSetupPosition = TRUE;
5500       break;
5501     case VariantCrazyhouse:
5502     case VariantBughouse:
5503       pieces = FIDEArray;
5504       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5505       gameInfo.holdingsSize = 5;
5506       break;
5507     case VariantWildCastle:
5508       pieces = FIDEArray;
5509       /* !!?shuffle with kings guaranteed to be on d or e file */
5510       shuffleOpenings = 1;
5511       break;
5512     case VariantNoCastle:
5513       pieces = FIDEArray;
5514       nrCastlingRights = 0;
5515       /* !!?unconstrained back-rank shuffle */
5516       shuffleOpenings = 1;
5517       break;
5518     }
5519
5520     overrule = 0;
5521     if(appData.NrFiles >= 0) {
5522         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5523         gameInfo.boardWidth = appData.NrFiles;
5524     }
5525     if(appData.NrRanks >= 0) {
5526         gameInfo.boardHeight = appData.NrRanks;
5527     }
5528     if(appData.holdingsSize >= 0) {
5529         i = appData.holdingsSize;
5530         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5531         gameInfo.holdingsSize = i;
5532     }
5533     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5534     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5535         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5536
5537     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5538     if(pawnRow < 1) pawnRow = 1;
5539     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5540
5541     /* User pieceToChar list overrules defaults */
5542     if(appData.pieceToCharTable != NULL)
5543         SetCharTable(pieceToChar, appData.pieceToCharTable);
5544
5545     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5546
5547         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5548             s = (ChessSquare) 0; /* account holding counts in guard band */
5549         for( i=0; i<BOARD_HEIGHT; i++ )
5550             initialPosition[i][j] = s;
5551
5552         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5553         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5554         initialPosition[pawnRow][j] = WhitePawn;
5555         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5556         if(gameInfo.variant == VariantXiangqi) {
5557             if(j&1) {
5558                 initialPosition[pawnRow][j] =
5559                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5560                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5561                    initialPosition[2][j] = WhiteCannon;
5562                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5563                 }
5564             }
5565         }
5566         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5567     }
5568     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5569
5570             j=BOARD_LEFT+1;
5571             initialPosition[1][j] = WhiteBishop;
5572             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5573             j=BOARD_RGHT-2;
5574             initialPosition[1][j] = WhiteRook;
5575             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5576     }
5577
5578     if( nrCastlingRights == -1) {
5579         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5580         /*       This sets default castling rights from none to normal corners   */
5581         /* Variants with other castling rights must set them themselves above    */
5582         nrCastlingRights = 6;
5583
5584         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5585         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5586         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5587         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5588         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5589         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5590      }
5591
5592      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5593      if(gameInfo.variant == VariantGreat) { // promotion commoners
5594         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5595         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5596         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5597         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5598      }
5599      if( gameInfo.variant == VariantSChess ) {
5600       initialPosition[1][0] = BlackMarshall;
5601       initialPosition[2][0] = BlackAngel;
5602       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5603       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5604       initialPosition[1][1] = initialPosition[2][1] = 
5605       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5606      }
5607   if (appData.debugMode) {
5608     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5609   }
5610     if(shuffleOpenings) {
5611         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5612         startedFromSetupPosition = TRUE;
5613     }
5614     if(startedFromPositionFile) {
5615       /* [HGM] loadPos: use PositionFile for every new game */
5616       CopyBoard(initialPosition, filePosition);
5617       for(i=0; i<nrCastlingRights; i++)
5618           initialRights[i] = filePosition[CASTLING][i];
5619       startedFromSetupPosition = TRUE;
5620     }
5621
5622     CopyBoard(boards[0], initialPosition);
5623
5624     if(oldx != gameInfo.boardWidth ||
5625        oldy != gameInfo.boardHeight ||
5626        oldv != gameInfo.variant ||
5627        oldh != gameInfo.holdingsWidth
5628                                          )
5629             InitDrawingSizes(-2 ,0);
5630
5631     oldv = gameInfo.variant;
5632     if (redraw)
5633       DrawPosition(TRUE, boards[currentMove]);
5634 }
5635
5636 void
5637 SendBoard(cps, moveNum)
5638      ChessProgramState *cps;
5639      int moveNum;
5640 {
5641     char message[MSG_SIZ];
5642
5643     if (cps->useSetboard) {
5644       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5645       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5646       SendToProgram(message, cps);
5647       free(fen);
5648
5649     } else {
5650       ChessSquare *bp;
5651       int i, j;
5652       /* Kludge to set black to move, avoiding the troublesome and now
5653        * deprecated "black" command.
5654        */
5655       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5656         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5657
5658       SendToProgram("edit\n", cps);
5659       SendToProgram("#\n", cps);
5660       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5661         bp = &boards[moveNum][i][BOARD_LEFT];
5662         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5663           if ((int) *bp < (int) BlackPawn) {
5664             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5665                     AAA + j, ONE + i);
5666             if(message[0] == '+' || message[0] == '~') {
5667               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5668                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5669                         AAA + j, ONE + i);
5670             }
5671             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5672                 message[1] = BOARD_RGHT   - 1 - j + '1';
5673                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5674             }
5675             SendToProgram(message, cps);
5676           }
5677         }
5678       }
5679
5680       SendToProgram("c\n", cps);
5681       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5682         bp = &boards[moveNum][i][BOARD_LEFT];
5683         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5684           if (((int) *bp != (int) EmptySquare)
5685               && ((int) *bp >= (int) BlackPawn)) {
5686             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5687                     AAA + j, ONE + i);
5688             if(message[0] == '+' || message[0] == '~') {
5689               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5690                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5691                         AAA + j, ONE + i);
5692             }
5693             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5694                 message[1] = BOARD_RGHT   - 1 - j + '1';
5695                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5696             }
5697             SendToProgram(message, cps);
5698           }
5699         }
5700       }
5701
5702       SendToProgram(".\n", cps);
5703     }
5704     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5705 }
5706
5707 static int autoQueen; // [HGM] oneclick
5708
5709 int
5710 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5711 {
5712     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5713     /* [HGM] add Shogi promotions */
5714     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5715     ChessSquare piece;
5716     ChessMove moveType;
5717     Boolean premove;
5718
5719     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5720     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5721
5722     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5723       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5724         return FALSE;
5725
5726     piece = boards[currentMove][fromY][fromX];
5727     if(gameInfo.variant == VariantShogi) {
5728         promotionZoneSize = BOARD_HEIGHT/3;
5729         highestPromotingPiece = (int)WhiteFerz;
5730     } else if(gameInfo.variant == VariantMakruk) {
5731         promotionZoneSize = 3;
5732     }
5733
5734     // Treat Lance as Pawn when it is not representing Amazon
5735     if(gameInfo.variant != VariantSuper) {
5736         if(piece == WhiteLance) piece = WhitePawn; else
5737         if(piece == BlackLance) piece = BlackPawn;
5738     }
5739
5740     // next weed out all moves that do not touch the promotion zone at all
5741     if((int)piece >= BlackPawn) {
5742         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5743              return FALSE;
5744         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5745     } else {
5746         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5747            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5748     }
5749
5750     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5751
5752     // weed out mandatory Shogi promotions
5753     if(gameInfo.variant == VariantShogi) {
5754         if(piece >= BlackPawn) {
5755             if(toY == 0 && piece == BlackPawn ||
5756                toY == 0 && piece == BlackQueen ||
5757                toY <= 1 && piece == BlackKnight) {
5758                 *promoChoice = '+';
5759                 return FALSE;
5760             }
5761         } else {
5762             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5763                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5764                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5765                 *promoChoice = '+';
5766                 return FALSE;
5767             }
5768         }
5769     }
5770
5771     // weed out obviously illegal Pawn moves
5772     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5773         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5774         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5775         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5776         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5777         // note we are not allowed to test for valid (non-)capture, due to premove
5778     }
5779
5780     // we either have a choice what to promote to, or (in Shogi) whether to promote
5781     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5782         *promoChoice = PieceToChar(BlackFerz);  // no choice
5783         return FALSE;
5784     }
5785     // no sense asking what we must promote to if it is going to explode...
5786     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5787         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5788         return FALSE;
5789     }
5790     // give caller the default choice even if we will not make it
5791     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5792          *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5793     else if(gameInfo.variant == VariantSpartan)
5794          *promoChoice = ToLower(PieceToChar(toY ? WhiteQueen : BlackAngel));
5795     else if(gameInfo.variant == VariantShogi)
5796          *promoChoice = '+';
5797     else *promoChoice =  ToLower(PieceToChar(toY ? WhiteQueen : BlackQueen));
5798     if(*promoChoice == '.') *promoChoice = ToLower(PieceToChar(piece)); // safety catch, to make sure promoChoice is a defined piece
5799     if(autoQueen) return FALSE; // predetermined
5800
5801     // suppress promotion popup on illegal moves that are not premoves
5802     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5803               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5804     if(appData.testLegality && !premove) {
5805         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5806                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5807         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5808             return FALSE;
5809     }
5810
5811     return TRUE;
5812 }
5813
5814 int
5815 InPalace(row, column)
5816      int row, column;
5817 {   /* [HGM] for Xiangqi */
5818     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5819          column < (BOARD_WIDTH + 4)/2 &&
5820          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5821     return FALSE;
5822 }
5823
5824 int
5825 PieceForSquare (x, y)
5826      int x;
5827      int y;
5828 {
5829   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5830      return -1;
5831   else
5832      return boards[currentMove][y][x];
5833 }
5834
5835 int
5836 OKToStartUserMove(x, y)
5837      int x, y;
5838 {
5839     ChessSquare from_piece;
5840     int white_piece;
5841
5842     if (matchMode) return FALSE;
5843     if (gameMode == EditPosition) return TRUE;
5844
5845     if (x >= 0 && y >= 0)
5846       from_piece = boards[currentMove][y][x];
5847     else
5848       from_piece = EmptySquare;
5849
5850     if (from_piece == EmptySquare) return FALSE;
5851
5852     white_piece = (int)from_piece >= (int)WhitePawn &&
5853       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5854
5855     switch (gameMode) {
5856       case PlayFromGameFile:
5857       case AnalyzeFile:
5858       case TwoMachinesPlay:
5859       case EndOfGame:
5860         return FALSE;
5861
5862       case IcsObserving:
5863       case IcsIdle:
5864         return FALSE;
5865
5866       case MachinePlaysWhite:
5867       case IcsPlayingBlack:
5868         if (appData.zippyPlay) return FALSE;
5869         if (white_piece) {
5870             DisplayMoveError(_("You are playing Black"));
5871             return FALSE;
5872         }
5873         break;
5874
5875       case MachinePlaysBlack:
5876       case IcsPlayingWhite:
5877         if (appData.zippyPlay) return FALSE;
5878         if (!white_piece) {
5879             DisplayMoveError(_("You are playing White"));
5880             return FALSE;
5881         }
5882         break;
5883
5884       case EditGame:
5885         if (!white_piece && WhiteOnMove(currentMove)) {
5886             DisplayMoveError(_("It is White's turn"));
5887             return FALSE;
5888         }
5889         if (white_piece && !WhiteOnMove(currentMove)) {
5890             DisplayMoveError(_("It is Black's turn"));
5891             return FALSE;
5892         }
5893         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5894             /* Editing correspondence game history */
5895             /* Could disallow this or prompt for confirmation */
5896             cmailOldMove = -1;
5897         }
5898         break;
5899
5900       case BeginningOfGame:
5901         if (appData.icsActive) return FALSE;
5902         if (!appData.noChessProgram) {
5903             if (!white_piece) {
5904                 DisplayMoveError(_("You are playing White"));
5905                 return FALSE;
5906             }
5907         }
5908         break;
5909
5910       case Training:
5911         if (!white_piece && WhiteOnMove(currentMove)) {
5912             DisplayMoveError(_("It is White's turn"));
5913             return FALSE;
5914         }
5915         if (white_piece && !WhiteOnMove(currentMove)) {
5916             DisplayMoveError(_("It is Black's turn"));
5917             return FALSE;
5918         }
5919         break;
5920
5921       default:
5922       case IcsExamining:
5923         break;
5924     }
5925     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5926         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5927         && gameMode != AnalyzeFile && gameMode != Training) {
5928         DisplayMoveError(_("Displayed position is not current"));
5929         return FALSE;
5930     }
5931     return TRUE;
5932 }
5933
5934 Boolean
5935 OnlyMove(int *x, int *y, Boolean captures) {
5936     DisambiguateClosure cl;
5937     if (appData.zippyPlay) return FALSE;
5938     switch(gameMode) {
5939       case MachinePlaysBlack:
5940       case IcsPlayingWhite:
5941       case BeginningOfGame:
5942         if(!WhiteOnMove(currentMove)) return FALSE;
5943         break;
5944       case MachinePlaysWhite:
5945       case IcsPlayingBlack:
5946         if(WhiteOnMove(currentMove)) return FALSE;
5947         break;
5948       case EditGame:
5949         break;
5950       default:
5951         return FALSE;
5952     }
5953     cl.pieceIn = EmptySquare;
5954     cl.rfIn = *y;
5955     cl.ffIn = *x;
5956     cl.rtIn = -1;
5957     cl.ftIn = -1;
5958     cl.promoCharIn = NULLCHAR;
5959     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5960     if( cl.kind == NormalMove ||
5961         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5962         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5963         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5964       fromX = cl.ff;
5965       fromY = cl.rf;
5966       *x = cl.ft;
5967       *y = cl.rt;
5968       return TRUE;
5969     }
5970     if(cl.kind != ImpossibleMove) return FALSE;
5971     cl.pieceIn = EmptySquare;
5972     cl.rfIn = -1;
5973     cl.ffIn = -1;
5974     cl.rtIn = *y;
5975     cl.ftIn = *x;
5976     cl.promoCharIn = NULLCHAR;
5977     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5978     if( cl.kind == NormalMove ||
5979         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5980         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5981         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5982       fromX = cl.ff;
5983       fromY = cl.rf;
5984       *x = cl.ft;
5985       *y = cl.rt;
5986       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5987       return TRUE;
5988     }
5989     return FALSE;
5990 }
5991
5992 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5993 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5994 int lastLoadGameUseList = FALSE;
5995 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5996 ChessMove lastLoadGameStart = EndOfFile;
5997
5998 void
5999 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6000      int fromX, fromY, toX, toY;
6001      int promoChar;
6002 {
6003     ChessMove moveType;
6004     ChessSquare pdown, pup;
6005
6006     /* Check if the user is playing in turn.  This is complicated because we
6007        let the user "pick up" a piece before it is his turn.  So the piece he
6008        tried to pick up may have been captured by the time he puts it down!
6009        Therefore we use the color the user is supposed to be playing in this
6010        test, not the color of the piece that is currently on the starting
6011        square---except in EditGame mode, where the user is playing both
6012        sides; fortunately there the capture race can't happen.  (It can
6013        now happen in IcsExamining mode, but that's just too bad.  The user
6014        will get a somewhat confusing message in that case.)
6015        */
6016
6017     switch (gameMode) {
6018       case PlayFromGameFile:
6019       case AnalyzeFile:
6020       case TwoMachinesPlay:
6021       case EndOfGame:
6022       case IcsObserving:
6023       case IcsIdle:
6024         /* We switched into a game mode where moves are not accepted,
6025            perhaps while the mouse button was down. */
6026         return;
6027
6028       case MachinePlaysWhite:
6029         /* User is moving for Black */
6030         if (WhiteOnMove(currentMove)) {
6031             DisplayMoveError(_("It is White's turn"));
6032             return;
6033         }
6034         break;
6035
6036       case MachinePlaysBlack:
6037         /* User is moving for White */
6038         if (!WhiteOnMove(currentMove)) {
6039             DisplayMoveError(_("It is Black's turn"));
6040             return;
6041         }
6042         break;
6043
6044       case EditGame:
6045       case IcsExamining:
6046       case BeginningOfGame:
6047       case AnalyzeMode:
6048       case Training:
6049         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6050         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6051             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6052             /* User is moving for Black */
6053             if (WhiteOnMove(currentMove)) {
6054                 DisplayMoveError(_("It is White's turn"));
6055                 return;
6056             }
6057         } else {
6058             /* User is moving for White */
6059             if (!WhiteOnMove(currentMove)) {
6060                 DisplayMoveError(_("It is Black's turn"));
6061                 return;
6062             }
6063         }
6064         break;
6065
6066       case IcsPlayingBlack:
6067         /* User is moving for Black */
6068         if (WhiteOnMove(currentMove)) {
6069             if (!appData.premove) {
6070                 DisplayMoveError(_("It is White's turn"));
6071             } else if (toX >= 0 && toY >= 0) {
6072                 premoveToX = toX;
6073                 premoveToY = toY;
6074                 premoveFromX = fromX;
6075                 premoveFromY = fromY;
6076                 premovePromoChar = promoChar;
6077                 gotPremove = 1;
6078                 if (appData.debugMode)
6079                     fprintf(debugFP, "Got premove: fromX %d,"
6080                             "fromY %d, toX %d, toY %d\n",
6081                             fromX, fromY, toX, toY);
6082             }
6083             return;
6084         }
6085         break;
6086
6087       case IcsPlayingWhite:
6088         /* User is moving for White */
6089         if (!WhiteOnMove(currentMove)) {
6090             if (!appData.premove) {
6091                 DisplayMoveError(_("It is Black's turn"));
6092             } else if (toX >= 0 && toY >= 0) {
6093                 premoveToX = toX;
6094                 premoveToY = toY;
6095                 premoveFromX = fromX;
6096                 premoveFromY = fromY;
6097                 premovePromoChar = promoChar;
6098                 gotPremove = 1;
6099                 if (appData.debugMode)
6100                     fprintf(debugFP, "Got premove: fromX %d,"
6101                             "fromY %d, toX %d, toY %d\n",
6102                             fromX, fromY, toX, toY);
6103             }
6104             return;
6105         }
6106         break;
6107
6108       default:
6109         break;
6110
6111       case EditPosition:
6112         /* EditPosition, empty square, or different color piece;
6113            click-click move is possible */
6114         if (toX == -2 || toY == -2) {
6115             boards[0][fromY][fromX] = EmptySquare;
6116             DrawPosition(FALSE, boards[currentMove]);
6117             return;
6118         } else if (toX >= 0 && toY >= 0) {
6119             boards[0][toY][toX] = boards[0][fromY][fromX];
6120             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6121                 if(boards[0][fromY][0] != EmptySquare) {
6122                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6123                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6124                 }
6125             } else
6126             if(fromX == BOARD_RGHT+1) {
6127                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6128                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6129                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6130                 }
6131             } else
6132             boards[0][fromY][fromX] = EmptySquare;
6133             DrawPosition(FALSE, boards[currentMove]);
6134             return;
6135         }
6136         return;
6137     }
6138
6139     if(toX < 0 || toY < 0) return;
6140     pdown = boards[currentMove][fromY][fromX];
6141     pup = boards[currentMove][toY][toX];
6142
6143     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6144     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6145          if( pup != EmptySquare ) return;
6146          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6147            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6148                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6149            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6150            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6151            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6152            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6153          fromY = DROP_RANK;
6154     }
6155
6156     /* [HGM] always test for legality, to get promotion info */
6157     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6158                                          fromY, fromX, toY, toX, promoChar);
6159     /* [HGM] but possibly ignore an IllegalMove result */
6160     if (appData.testLegality) {
6161         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6162             DisplayMoveError(_("Illegal move"));
6163             return;
6164         }
6165     }
6166
6167     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6168 }
6169
6170 /* Common tail of UserMoveEvent and DropMenuEvent */
6171 int
6172 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6173      ChessMove moveType;
6174      int fromX, fromY, toX, toY;
6175      /*char*/int promoChar;
6176 {
6177     char *bookHit = 0;
6178
6179     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6180         // [HGM] superchess: suppress promotions to non-available piece
6181         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6182         if(WhiteOnMove(currentMove)) {
6183             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6184         } else {
6185             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6186         }
6187     }
6188
6189     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6190        move type in caller when we know the move is a legal promotion */
6191     if(moveType == NormalMove && promoChar)
6192         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6193
6194     /* [HGM] <popupFix> The following if has been moved here from
6195        UserMoveEvent(). Because it seemed to belong here (why not allow
6196        piece drops in training games?), and because it can only be
6197        performed after it is known to what we promote. */
6198     if (gameMode == Training) {
6199       /* compare the move played on the board to the next move in the
6200        * game. If they match, display the move and the opponent's response.
6201        * If they don't match, display an error message.
6202        */
6203       int saveAnimate;
6204       Board testBoard;
6205       CopyBoard(testBoard, boards[currentMove]);
6206       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6207
6208       if (CompareBoards(testBoard, boards[currentMove+1])) {
6209         ForwardInner(currentMove+1);
6210
6211         /* Autoplay the opponent's response.
6212          * if appData.animate was TRUE when Training mode was entered,
6213          * the response will be animated.
6214          */
6215         saveAnimate = appData.animate;
6216         appData.animate = animateTraining;
6217         ForwardInner(currentMove+1);
6218         appData.animate = saveAnimate;
6219
6220         /* check for the end of the game */
6221         if (currentMove >= forwardMostMove) {
6222           gameMode = PlayFromGameFile;
6223           ModeHighlight();
6224           SetTrainingModeOff();
6225           DisplayInformation(_("End of game"));
6226         }
6227       } else {
6228         DisplayError(_("Incorrect move"), 0);
6229       }
6230       return 1;
6231     }
6232
6233   /* Ok, now we know that the move is good, so we can kill
6234      the previous line in Analysis Mode */
6235   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6236                                 && currentMove < forwardMostMove) {
6237     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6238     else forwardMostMove = currentMove;
6239   }
6240
6241   /* If we need the chess program but it's dead, restart it */
6242   ResurrectChessProgram();
6243
6244   /* A user move restarts a paused game*/
6245   if (pausing)
6246     PauseEvent();
6247
6248   thinkOutput[0] = NULLCHAR;
6249
6250   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6251
6252   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6253     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6254     return 1;
6255   }
6256
6257   if (gameMode == BeginningOfGame) {
6258     if (appData.noChessProgram) {
6259       gameMode = EditGame;
6260       SetGameInfo();
6261     } else {
6262       char buf[MSG_SIZ];
6263       gameMode = MachinePlaysBlack;
6264       StartClocks();
6265       SetGameInfo();
6266       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6267       DisplayTitle(buf);
6268       if (first.sendName) {
6269         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6270         SendToProgram(buf, &first);
6271       }
6272       StartClocks();
6273     }
6274     ModeHighlight();
6275   }
6276
6277   /* Relay move to ICS or chess engine */
6278   if (appData.icsActive) {
6279     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6280         gameMode == IcsExamining) {
6281       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6282         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6283         SendToICS("draw ");
6284         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6285       }
6286       // also send plain move, in case ICS does not understand atomic claims
6287       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6288       ics_user_moved = 1;
6289     }
6290   } else {
6291     if (first.sendTime && (gameMode == BeginningOfGame ||
6292                            gameMode == MachinePlaysWhite ||
6293                            gameMode == MachinePlaysBlack)) {
6294       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6295     }
6296     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6297          // [HGM] book: if program might be playing, let it use book
6298         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6299         first.maybeThinking = TRUE;
6300     } else SendMoveToProgram(forwardMostMove-1, &first);
6301     if (currentMove == cmailOldMove + 1) {
6302       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6303     }
6304   }
6305
6306   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6307
6308   switch (gameMode) {
6309   case EditGame:
6310     if(appData.testLegality)
6311     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6312     case MT_NONE:
6313     case MT_CHECK:
6314       break;
6315     case MT_CHECKMATE:
6316     case MT_STAINMATE:
6317       if (WhiteOnMove(currentMove)) {
6318         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6319       } else {
6320         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6321       }
6322       break;
6323     case MT_STALEMATE:
6324       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6325       break;
6326     }
6327     break;
6328
6329   case MachinePlaysBlack:
6330   case MachinePlaysWhite:
6331     /* disable certain menu options while machine is thinking */
6332     SetMachineThinkingEnables();
6333     break;
6334
6335   default:
6336     break;
6337   }
6338
6339   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6340
6341   if(bookHit) { // [HGM] book: simulate book reply
6342         static char bookMove[MSG_SIZ]; // a bit generous?
6343
6344         programStats.nodes = programStats.depth = programStats.time =
6345         programStats.score = programStats.got_only_move = 0;
6346         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6347
6348         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6349         strcat(bookMove, bookHit);
6350         HandleMachineMove(bookMove, &first);
6351   }
6352   return 1;
6353 }
6354
6355 void
6356 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6357      Board board;
6358      int flags;
6359      ChessMove kind;
6360      int rf, ff, rt, ft;
6361      VOIDSTAR closure;
6362 {
6363     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6364     Markers *m = (Markers *) closure;
6365     if(rf == fromY && ff == fromX)
6366         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6367                          || kind == WhiteCapturesEnPassant
6368                          || kind == BlackCapturesEnPassant);
6369     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6370 }
6371
6372 void
6373 MarkTargetSquares(int clear)
6374 {
6375   int x, y;
6376   if(!appData.markers || !appData.highlightDragging ||
6377      !appData.testLegality || gameMode == EditPosition) return;
6378   if(clear) {
6379     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6380   } else {
6381     int capt = 0;
6382     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6383     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6384       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6385       if(capt)
6386       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6387     }
6388   }
6389   DrawPosition(TRUE, NULL);
6390 }
6391
6392 int
6393 Explode(Board board, int fromX, int fromY, int toX, int toY)
6394 {
6395     if(gameInfo.variant == VariantAtomic &&
6396        (board[toY][toX] != EmptySquare ||                     // capture?
6397         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6398                          board[fromY][fromX] == BlackPawn   )
6399       )) {
6400         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6401         return TRUE;
6402     }
6403     return FALSE;
6404 }
6405
6406 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6407
6408 void LeftClick(ClickType clickType, int xPix, int yPix)
6409 {
6410     int x, y;
6411     Boolean saveAnimate;
6412     static int second = 0, promotionChoice = 0, dragging = 0;
6413     char promoChoice = NULLCHAR;
6414
6415     if(appData.seekGraph && appData.icsActive && loggedOn &&
6416         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6417         SeekGraphClick(clickType, xPix, yPix, 0);
6418         return;
6419     }
6420
6421     if (clickType == Press) ErrorPopDown();
6422     MarkTargetSquares(1);
6423
6424     x = EventToSquare(xPix, BOARD_WIDTH);
6425     y = EventToSquare(yPix, BOARD_HEIGHT);
6426     if (!flipView && y >= 0) {
6427         y = BOARD_HEIGHT - 1 - y;
6428     }
6429     if (flipView && x >= 0) {
6430         x = BOARD_WIDTH - 1 - x;
6431     }
6432
6433     if(promoSweep != EmptySquare) {
6434         char promoChar = ToLower(PieceToChar(promoSweep));
6435         if(gameInfo.variant == VariantShogi) promoChar = promoSweep == boards[currentMove][fromY][fromX] ? '=' : '+';
6436         saveAnimate = appData.animate; appData.animate = FALSE;
6437         UserMoveEvent(fromX, fromY, toX, toY, promoChar);
6438         appData.animate = saveAnimate;
6439         promoSweep = EmptySquare;
6440         DrawPosition(FALSE, boards[currentMove]);
6441         fromX = fromY = -1;
6442         return;
6443     }
6444
6445     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6446         if(clickType == Release) return; // ignore upclick of click-click destination
6447         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6448         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6449         if(gameInfo.holdingsWidth &&
6450                 (WhiteOnMove(currentMove)
6451                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6452                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6453             // click in right holdings, for determining promotion piece
6454             ChessSquare p = boards[currentMove][y][x];
6455             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6456             if(p != EmptySquare) {
6457                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6458                 fromX = fromY = -1;
6459                 return;
6460             }
6461         }
6462         DrawPosition(FALSE, boards[currentMove]);
6463         return;
6464     }
6465
6466     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6467     if(clickType == Press
6468             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6469               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6470               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6471         return;
6472
6473     autoQueen = appData.alwaysPromoteToQueen;
6474
6475     if (fromX == -1) {
6476       gatingPiece = EmptySquare;
6477       if (clickType != Press) {
6478         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6479             DragPieceEnd(xPix, yPix); dragging = 0;
6480             DrawPosition(FALSE, NULL);
6481         }
6482         return;
6483       }
6484       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6485             /* First square */
6486             if (OKToStartUserMove(x, y)) {
6487                 fromX = x;
6488                 fromY = y;
6489                 second = 0;
6490                 MarkTargetSquares(0);
6491                 DragPieceBegin(xPix, yPix); dragging = 1;
6492                 if (appData.highlightDragging) {
6493                     SetHighlights(x, y, -1, -1);
6494                 }
6495             }
6496             return;
6497         }
6498     }
6499
6500     /* fromX != -1 */
6501     if (clickType == Press && gameMode != EditPosition) {
6502         ChessSquare fromP;
6503         ChessSquare toP;
6504         int frc;
6505
6506         // ignore off-board to clicks
6507         if(y < 0 || x < 0) return;
6508
6509         /* Check if clicking again on the same color piece */
6510         fromP = boards[currentMove][fromY][fromX];
6511         toP = boards[currentMove][y][x];
6512         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6513         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6514              WhitePawn <= toP && toP <= WhiteKing &&
6515              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6516              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6517             (BlackPawn <= fromP && fromP <= BlackKing &&
6518              BlackPawn <= toP && toP <= BlackKing &&
6519              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6520              !(fromP == BlackKing && toP == BlackRook && frc))) {
6521             /* Clicked again on same color piece -- changed his mind */
6522             second = (x == fromX && y == fromY);
6523            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6524             if (appData.highlightDragging) {
6525                 SetHighlights(x, y, -1, -1);
6526             } else {
6527                 ClearHighlights();
6528             }
6529             if (OKToStartUserMove(x, y)) {
6530                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6531                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6532                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6533                  gatingPiece = boards[currentMove][fromY][fromX];
6534                 else gatingPiece = EmptySquare;
6535                 fromX = x;
6536                 fromY = y; dragging = 1;
6537                 MarkTargetSquares(0);
6538                 DragPieceBegin(xPix, yPix);
6539             }
6540            }
6541            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6542            second = FALSE; 
6543         }
6544         // ignore clicks on holdings
6545         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6546     }
6547
6548     if (clickType == Release && x == fromX && y == fromY) {
6549         DragPieceEnd(xPix, yPix); dragging = 0;
6550         if (appData.animateDragging) {
6551             /* Undo animation damage if any */
6552             DrawPosition(FALSE, NULL);
6553         }
6554         if (second) {
6555             /* Second up/down in same square; just abort move */
6556             second = 0;
6557             fromX = fromY = -1;
6558             gatingPiece = EmptySquare;
6559             ClearHighlights();
6560             gotPremove = 0;
6561             ClearPremoveHighlights();
6562         } else {
6563             /* First upclick in same square; start click-click mode */
6564             SetHighlights(x, y, -1, -1);
6565         }
6566         return;
6567     }
6568
6569     /* we now have a different from- and (possibly off-board) to-square */
6570     /* Completed move */
6571     toX = x;
6572     toY = y;
6573     saveAnimate = appData.animate;
6574     if (clickType == Press) {
6575         /* Finish clickclick move */
6576         if (appData.animate || appData.highlightLastMove) {
6577             SetHighlights(fromX, fromY, toX, toY);
6578         } else {
6579             ClearHighlights();
6580         }
6581     } else {
6582         /* Finish drag move */
6583         if (appData.highlightLastMove) {
6584             SetHighlights(fromX, fromY, toX, toY);
6585         } else {
6586             ClearHighlights();
6587         }
6588         DragPieceEnd(xPix, yPix); dragging = 0;
6589         /* Don't animate move and drag both */
6590         appData.animate = FALSE;
6591     }
6592
6593     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6594     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6595         ChessSquare piece = boards[currentMove][fromY][fromX];
6596         if(gameMode == EditPosition && piece != EmptySquare &&
6597            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6598             int n;
6599
6600             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6601                 n = PieceToNumber(piece - (int)BlackPawn);
6602                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6603                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6604                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6605             } else
6606             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6607                 n = PieceToNumber(piece);
6608                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6609                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6610                 boards[currentMove][n][BOARD_WIDTH-2]++;
6611             }
6612             boards[currentMove][fromY][fromX] = EmptySquare;
6613         }
6614         ClearHighlights();
6615         fromX = fromY = -1;
6616         DrawPosition(TRUE, boards[currentMove]);
6617         return;
6618     }
6619
6620     // off-board moves should not be highlighted
6621     if(x < 0 || y < 0) ClearHighlights();
6622
6623     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6624
6625     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6626         SetHighlights(fromX, fromY, toX, toY);
6627         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6628             // [HGM] super: promotion to captured piece selected from holdings
6629             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6630             promotionChoice = TRUE;
6631             // kludge follows to temporarily execute move on display, without promoting yet
6632             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6633             boards[currentMove][toY][toX] = p;
6634             DrawPosition(FALSE, boards[currentMove]);
6635             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6636             boards[currentMove][toY][toX] = q;
6637             DisplayMessage("Click in holdings to choose piece", "");
6638             return;
6639         }
6640         if(appData.sweepSelect && clickType == Press) {
6641              lastX = xPix; lastY = yPix;
6642              ChessSquare piece = boards[currentMove][fromY][fromX];
6643              promoSweep = CharToPiece((piece >= BlackPawn ? ToLower : ToUpper)(promoChoice));
6644              if(promoChoice == '+') promoSweep = PROMOTED piece;
6645              Sweep(0);
6646         } else PromotionPopUp();
6647     } else {
6648         int oldMove = currentMove;
6649         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6650         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6651         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6652         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6653            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6654             DrawPosition(TRUE, boards[currentMove]);
6655         fromX = fromY = -1;
6656     }
6657     appData.animate = saveAnimate;
6658     if (appData.animate || appData.animateDragging) {
6659         /* Undo animation damage if needed */
6660         DrawPosition(FALSE, NULL);
6661     }
6662 }
6663
6664 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6665 {   // front-end-free part taken out of PieceMenuPopup
6666     int whichMenu; int xSqr, ySqr;
6667
6668     if(seekGraphUp) { // [HGM] seekgraph
6669         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6670         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6671         return -2;
6672     }
6673
6674     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6675          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6676         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6677         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6678         if(action == Press)   {
6679             originalFlip = flipView;
6680             flipView = !flipView; // temporarily flip board to see game from partners perspective
6681             DrawPosition(TRUE, partnerBoard);
6682             DisplayMessage(partnerStatus, "");
6683             partnerUp = TRUE;
6684         } else if(action == Release) {
6685             flipView = originalFlip;
6686             DrawPosition(TRUE, boards[currentMove]);
6687             partnerUp = FALSE;
6688         }
6689         return -2;
6690     }
6691
6692     xSqr = EventToSquare(x, BOARD_WIDTH);
6693     ySqr = EventToSquare(y, BOARD_HEIGHT);
6694     if (action == Release) {
6695         if(pieceSweep != EmptySquare) {
6696             EditPositionMenuEvent(pieceSweep, toX, toY);
6697             pieceSweep = EmptySquare;
6698         } else UnLoadPV(); // [HGM] pv
6699     }
6700     if (action != Press) return -2; // return code to be ignored
6701     switch (gameMode) {
6702       case IcsExamining:
6703         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6704       case EditPosition:
6705         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6706         if (xSqr < 0 || ySqr < 0) return -1;
6707         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6708         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6709         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6710         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6711         NextPiece(0);
6712         return -2;\r
6713       case IcsObserving:
6714         if(!appData.icsEngineAnalyze) return -1;
6715       case IcsPlayingWhite:
6716       case IcsPlayingBlack:
6717         if(!appData.zippyPlay) goto noZip;
6718       case AnalyzeMode:
6719       case AnalyzeFile:
6720       case MachinePlaysWhite:
6721       case MachinePlaysBlack:
6722       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6723         if (!appData.dropMenu) {
6724           LoadPV(x, y);
6725           return 2; // flag front-end to grab mouse events
6726         }
6727         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6728            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6729       case EditGame:
6730       noZip:
6731         if (xSqr < 0 || ySqr < 0) return -1;
6732         if (!appData.dropMenu || appData.testLegality &&
6733             gameInfo.variant != VariantBughouse &&
6734             gameInfo.variant != VariantCrazyhouse) return -1;
6735         whichMenu = 1; // drop menu
6736         break;
6737       default:
6738         return -1;
6739     }
6740
6741     if (((*fromX = xSqr) < 0) ||
6742         ((*fromY = ySqr) < 0)) {
6743         *fromX = *fromY = -1;
6744         return -1;
6745     }
6746     if (flipView)
6747       *fromX = BOARD_WIDTH - 1 - *fromX;
6748     else
6749       *fromY = BOARD_HEIGHT - 1 - *fromY;
6750
6751     return whichMenu;
6752 }
6753
6754 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6755 {
6756 //    char * hint = lastHint;
6757     FrontEndProgramStats stats;
6758
6759     stats.which = cps == &first ? 0 : 1;
6760     stats.depth = cpstats->depth;
6761     stats.nodes = cpstats->nodes;
6762     stats.score = cpstats->score;
6763     stats.time = cpstats->time;
6764     stats.pv = cpstats->movelist;
6765     stats.hint = lastHint;
6766     stats.an_move_index = 0;
6767     stats.an_move_count = 0;
6768
6769     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6770         stats.hint = cpstats->move_name;
6771         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6772         stats.an_move_count = cpstats->nr_moves;
6773     }
6774
6775     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
6776
6777     SetProgramStats( &stats );
6778 }
6779
6780 void
6781 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6782 {       // count all piece types
6783         int p, f, r;
6784         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6785         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6786         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6787                 p = board[r][f];
6788                 pCnt[p]++;
6789                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6790                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6791                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6792                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6793                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6794                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6795         }
6796 }
6797
6798 int
6799 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6800 {
6801         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6802         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6803
6804         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6805         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6806         if(myPawns == 2 && nMine == 3) // KPP
6807             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6808         if(myPawns == 1 && nMine == 2) // KP
6809             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6810         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6811             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6812         if(myPawns) return FALSE;
6813         if(pCnt[WhiteRook+side])
6814             return pCnt[BlackRook-side] ||
6815                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6816                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6817                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6818         if(pCnt[WhiteCannon+side]) {
6819             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6820             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6821         }
6822         if(pCnt[WhiteKnight+side])
6823             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6824         return FALSE;
6825 }
6826
6827 int
6828 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6829 {
6830         VariantClass v = gameInfo.variant;
6831
6832         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6833         if(v == VariantShatranj) return TRUE; // always winnable through baring
6834         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6835         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6836
6837         if(v == VariantXiangqi) {
6838                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6839
6840                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6841                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6842                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6843                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6844                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6845                 if(stale) // we have at least one last-rank P plus perhaps C
6846                     return majors // KPKX
6847                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6848                 else // KCA*E*
6849                     return pCnt[WhiteFerz+side] // KCAK
6850                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6851                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6852                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6853
6854         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6855                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6856
6857                 if(nMine == 1) return FALSE; // bare King
6858                 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
6859                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6860                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6861                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6862                 if(pCnt[WhiteKnight+side])
6863                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6864                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6865                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6866                 if(nBishops)
6867                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6868                 if(pCnt[WhiteAlfil+side])
6869                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6870                 if(pCnt[WhiteWazir+side])
6871                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6872         }
6873
6874         return TRUE;
6875 }
6876
6877 int
6878 Adjudicate(ChessProgramState *cps)
6879 {       // [HGM] some adjudications useful with buggy engines
6880         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6881         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6882         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6883         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6884         int k, count = 0; static int bare = 1;
6885         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6886         Boolean canAdjudicate = !appData.icsActive;
6887
6888         // most tests only when we understand the game, i.e. legality-checking on
6889             if( appData.testLegality )
6890             {   /* [HGM] Some more adjudications for obstinate engines */
6891                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6892                 static int moveCount = 6;
6893                 ChessMove result;
6894                 char *reason = NULL;
6895
6896                 /* Count what is on board. */
6897                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6898
6899                 /* Some material-based adjudications that have to be made before stalemate test */
6900                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6901                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6902                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6903                      if(canAdjudicate && appData.checkMates) {
6904                          if(engineOpponent)
6905                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6906                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6907                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6908                          return 1;
6909                      }
6910                 }
6911
6912                 /* Bare King in Shatranj (loses) or Losers (wins) */
6913                 if( nrW == 1 || nrB == 1) {
6914                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6915                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6916                      if(canAdjudicate && appData.checkMates) {
6917                          if(engineOpponent)
6918                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6919                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6920                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6921                          return 1;
6922                      }
6923                   } else
6924                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6925                   {    /* bare King */
6926                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6927                         if(canAdjudicate && appData.checkMates) {
6928                             /* but only adjudicate if adjudication enabled */
6929                             if(engineOpponent)
6930                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6931                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6932                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6933                             return 1;
6934                         }
6935                   }
6936                 } else bare = 1;
6937
6938
6939             // don't wait for engine to announce game end if we can judge ourselves
6940             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6941               case MT_CHECK:
6942                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6943                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6944                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6945                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6946                             checkCnt++;
6947                         if(checkCnt >= 2) {
6948                             reason = "Xboard adjudication: 3rd check";
6949                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6950                             break;
6951                         }
6952                     }
6953                 }
6954               case MT_NONE:
6955               default:
6956                 break;
6957               case MT_STALEMATE:
6958               case MT_STAINMATE:
6959                 reason = "Xboard adjudication: Stalemate";
6960                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6961                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6962                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6963                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6964                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6965                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6966                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6967                                                                         EP_CHECKMATE : EP_WINS);
6968                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6969                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6970                 }
6971                 break;
6972               case MT_CHECKMATE:
6973                 reason = "Xboard adjudication: Checkmate";
6974                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6975                 break;
6976             }
6977
6978                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6979                     case EP_STALEMATE:
6980                         result = GameIsDrawn; break;
6981                     case EP_CHECKMATE:
6982                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6983                     case EP_WINS:
6984                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6985                     default:
6986                         result = EndOfFile;
6987                 }
6988                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6989                     if(engineOpponent)
6990                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6991                     GameEnds( result, reason, GE_XBOARD );
6992                     return 1;
6993                 }
6994
6995                 /* Next absolutely insufficient mating material. */
6996                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6997                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6998                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6999
7000                      /* always flag draws, for judging claims */
7001                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7002
7003                      if(canAdjudicate && appData.materialDraws) {
7004                          /* but only adjudicate them if adjudication enabled */
7005                          if(engineOpponent) {
7006                            SendToProgram("force\n", engineOpponent); // suppress reply
7007                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7008                          }
7009                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7010                          return 1;
7011                      }
7012                 }
7013
7014                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7015                 if(gameInfo.variant == VariantXiangqi ?
7016                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7017                  : nrW + nrB == 4 &&
7018                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7019                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7020                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7021                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7022                    ) ) {
7023                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7024                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7025                           if(engineOpponent) {
7026                             SendToProgram("force\n", engineOpponent); // suppress reply
7027                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7028                           }
7029                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7030                           return 1;
7031                      }
7032                 } else moveCount = 6;
7033             }
7034         if (appData.debugMode) { int i;
7035             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7036                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7037                     appData.drawRepeats);
7038             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7039               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7040
7041         }
7042
7043         // Repetition draws and 50-move rule can be applied independently of legality testing
7044
7045                 /* Check for rep-draws */
7046                 count = 0;
7047                 for(k = forwardMostMove-2;
7048                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7049                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7050                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7051                     k-=2)
7052                 {   int rights=0;
7053                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7054                         /* compare castling rights */
7055                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7056                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7057                                 rights++; /* King lost rights, while rook still had them */
7058                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7059                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7060                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7061                                    rights++; /* but at least one rook lost them */
7062                         }
7063                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7064                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7065                                 rights++;
7066                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7067                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7068                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7069                                    rights++;
7070                         }
7071                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7072                             && appData.drawRepeats > 1) {
7073                              /* adjudicate after user-specified nr of repeats */
7074                              int result = GameIsDrawn;
7075                              char *details = "XBoard adjudication: repetition draw";
7076                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7077                                 // [HGM] xiangqi: check for forbidden perpetuals
7078                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7079                                 for(m=forwardMostMove; m>k; m-=2) {
7080                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7081                                         ourPerpetual = 0; // the current mover did not always check
7082                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7083                                         hisPerpetual = 0; // the opponent did not always check
7084                                 }
7085                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7086                                                                         ourPerpetual, hisPerpetual);
7087                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7088                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7089                                     details = "Xboard adjudication: perpetual checking";
7090                                 } else
7091                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7092                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7093                                 } else
7094                                 // Now check for perpetual chases
7095                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7096                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7097                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7098                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7099                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7100                                         details = "Xboard adjudication: perpetual chasing";
7101                                     } else
7102                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7103                                         break; // Abort repetition-checking loop.
7104                                 }
7105                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7106                              }
7107                              if(engineOpponent) {
7108                                SendToProgram("force\n", engineOpponent); // suppress reply
7109                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7110                              }
7111                              GameEnds( result, details, GE_XBOARD );
7112                              return 1;
7113                         }
7114                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7115                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7116                     }
7117                 }
7118
7119                 /* Now we test for 50-move draws. Determine ply count */
7120                 count = forwardMostMove;
7121                 /* look for last irreversble move */
7122                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7123                     count--;
7124                 /* if we hit starting position, add initial plies */
7125                 if( count == backwardMostMove )
7126                     count -= initialRulePlies;
7127                 count = forwardMostMove - count;
7128                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7129                         // adjust reversible move counter for checks in Xiangqi
7130                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7131                         if(i < backwardMostMove) i = backwardMostMove;
7132                         while(i <= forwardMostMove) {
7133                                 lastCheck = inCheck; // check evasion does not count
7134                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7135                                 if(inCheck || lastCheck) count--; // check does not count
7136                                 i++;
7137                         }
7138                 }
7139                 if( count >= 100)
7140                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7141                          /* this is used to judge if draw claims are legal */
7142                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7143                          if(engineOpponent) {
7144                            SendToProgram("force\n", engineOpponent); // suppress reply
7145                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7146                          }
7147                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7148                          return 1;
7149                 }
7150
7151                 /* if draw offer is pending, treat it as a draw claim
7152                  * when draw condition present, to allow engines a way to
7153                  * claim draws before making their move to avoid a race
7154                  * condition occurring after their move
7155                  */
7156                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7157                          char *p = NULL;
7158                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7159                              p = "Draw claim: 50-move rule";
7160                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7161                              p = "Draw claim: 3-fold repetition";
7162                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7163                              p = "Draw claim: insufficient mating material";
7164                          if( p != NULL && canAdjudicate) {
7165                              if(engineOpponent) {
7166                                SendToProgram("force\n", engineOpponent); // suppress reply
7167                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7168                              }
7169                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7170                              return 1;
7171                          }
7172                 }
7173
7174                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7175                     if(engineOpponent) {
7176                       SendToProgram("force\n", engineOpponent); // suppress reply
7177                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7178                     }
7179                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7180                     return 1;
7181                 }
7182         return 0;
7183 }
7184
7185 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7186 {   // [HGM] book: this routine intercepts moves to simulate book replies
7187     char *bookHit = NULL;
7188
7189     //first determine if the incoming move brings opponent into his book
7190     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7191         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7192     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7193     if(bookHit != NULL && !cps->bookSuspend) {
7194         // make sure opponent is not going to reply after receiving move to book position
7195         SendToProgram("force\n", cps);
7196         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7197     }
7198     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7199     // now arrange restart after book miss
7200     if(bookHit) {
7201         // after a book hit we never send 'go', and the code after the call to this routine
7202         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7203         char buf[MSG_SIZ];
7204         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7205         SendToProgram(buf, cps);
7206         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7207     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7208         SendToProgram("go\n", cps);
7209         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7210     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7211         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7212             SendToProgram("go\n", cps);
7213         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7214     }
7215     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7216 }
7217
7218 char *savedMessage;
7219 ChessProgramState *savedState;
7220 void DeferredBookMove(void)
7221 {
7222         if(savedState->lastPing != savedState->lastPong)
7223                     ScheduleDelayedEvent(DeferredBookMove, 10);
7224         else
7225         HandleMachineMove(savedMessage, savedState);
7226 }
7227
7228 void
7229 HandleMachineMove(message, cps)
7230      char *message;
7231      ChessProgramState *cps;
7232 {
7233     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7234     char realname[MSG_SIZ];
7235     int fromX, fromY, toX, toY;
7236     ChessMove moveType;
7237     char promoChar;
7238     char *p;
7239     int machineWhite;
7240     char *bookHit;
7241
7242     cps->userError = 0;
7243
7244 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7245     /*
7246      * Kludge to ignore BEL characters
7247      */
7248     while (*message == '\007') message++;
7249
7250     /*
7251      * [HGM] engine debug message: ignore lines starting with '#' character
7252      */
7253     if(cps->debug && *message == '#') return;
7254
7255     /*
7256      * Look for book output
7257      */
7258     if (cps == &first && bookRequested) {
7259         if (message[0] == '\t' || message[0] == ' ') {
7260             /* Part of the book output is here; append it */
7261             strcat(bookOutput, message);
7262             strcat(bookOutput, "  \n");
7263             return;
7264         } else if (bookOutput[0] != NULLCHAR) {
7265             /* All of book output has arrived; display it */
7266             char *p = bookOutput;
7267             while (*p != NULLCHAR) {
7268                 if (*p == '\t') *p = ' ';
7269                 p++;
7270             }
7271             DisplayInformation(bookOutput);
7272             bookRequested = FALSE;
7273             /* Fall through to parse the current output */
7274         }
7275     }
7276
7277     /*
7278      * Look for machine move.
7279      */
7280     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7281         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7282     {
7283         /* This method is only useful on engines that support ping */
7284         if (cps->lastPing != cps->lastPong) {
7285           if (gameMode == BeginningOfGame) {
7286             /* Extra move from before last new; ignore */
7287             if (appData.debugMode) {
7288                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7289             }
7290           } else {
7291             if (appData.debugMode) {
7292                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7293                         cps->which, gameMode);
7294             }
7295
7296             SendToProgram("undo\n", cps);
7297           }
7298           return;
7299         }
7300
7301         switch (gameMode) {
7302           case BeginningOfGame:
7303             /* Extra move from before last reset; ignore */
7304             if (appData.debugMode) {
7305                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7306             }
7307             return;
7308
7309           case EndOfGame:
7310           case IcsIdle:
7311           default:
7312             /* Extra move after we tried to stop.  The mode test is
7313                not a reliable way of detecting this problem, but it's
7314                the best we can do on engines that don't support ping.
7315             */
7316             if (appData.debugMode) {
7317                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7318                         cps->which, gameMode);
7319             }
7320             SendToProgram("undo\n", cps);
7321             return;
7322
7323           case MachinePlaysWhite:
7324           case IcsPlayingWhite:
7325             machineWhite = TRUE;
7326             break;
7327
7328           case MachinePlaysBlack:
7329           case IcsPlayingBlack:
7330             machineWhite = FALSE;
7331             break;
7332
7333           case TwoMachinesPlay:
7334             machineWhite = (cps->twoMachinesColor[0] == 'w');
7335             break;
7336         }
7337         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7338             if (appData.debugMode) {
7339                 fprintf(debugFP,
7340                         "Ignoring move out of turn by %s, gameMode %d"
7341                         ", forwardMost %d\n",
7342                         cps->which, gameMode, forwardMostMove);
7343             }
7344             return;
7345         }
7346
7347     if (appData.debugMode) { int f = forwardMostMove;
7348         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7349                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7350                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7351     }
7352         if(cps->alphaRank) AlphaRank(machineMove, 4);
7353         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7354                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7355             /* Machine move could not be parsed; ignore it. */
7356           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7357                     machineMove, _(cps->which));
7358             DisplayError(buf1, 0);
7359             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7360                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7361             if (gameMode == TwoMachinesPlay) {
7362               GameEnds(machineWhite ? BlackWins : WhiteWins,
7363                        buf1, GE_XBOARD);
7364             }
7365             return;
7366         }
7367
7368         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7369         /* So we have to redo legality test with true e.p. status here,  */
7370         /* to make sure an illegal e.p. capture does not slip through,   */
7371         /* to cause a forfeit on a justified illegal-move complaint      */
7372         /* of the opponent.                                              */
7373         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7374            ChessMove moveType;
7375            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7376                              fromY, fromX, toY, toX, promoChar);
7377             if (appData.debugMode) {
7378                 int i;
7379                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7380                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7381                 fprintf(debugFP, "castling rights\n");
7382             }
7383             if(moveType == IllegalMove) {
7384               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7385                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7386                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7387                            buf1, GE_XBOARD);
7388                 return;
7389            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7390            /* [HGM] Kludge to handle engines that send FRC-style castling
7391               when they shouldn't (like TSCP-Gothic) */
7392            switch(moveType) {
7393              case WhiteASideCastleFR:
7394              case BlackASideCastleFR:
7395                toX+=2;
7396                currentMoveString[2]++;
7397                break;
7398              case WhiteHSideCastleFR:
7399              case BlackHSideCastleFR:
7400                toX--;
7401                currentMoveString[2]--;
7402                break;
7403              default: ; // nothing to do, but suppresses warning of pedantic compilers
7404            }
7405         }
7406         hintRequested = FALSE;
7407         lastHint[0] = NULLCHAR;
7408         bookRequested = FALSE;
7409         /* Program may be pondering now */
7410         cps->maybeThinking = TRUE;
7411         if (cps->sendTime == 2) cps->sendTime = 1;
7412         if (cps->offeredDraw) cps->offeredDraw--;
7413
7414         /* [AS] Save move info*/
7415         pvInfoList[ forwardMostMove ].score = programStats.score;
7416         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7417         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7418
7419         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7420
7421         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7422         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7423             int count = 0;
7424
7425             while( count < adjudicateLossPlies ) {
7426                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7427
7428                 if( count & 1 ) {
7429                     score = -score; /* Flip score for winning side */
7430                 }
7431
7432                 if( score > adjudicateLossThreshold ) {
7433                     break;
7434                 }
7435
7436                 count++;
7437             }
7438
7439             if( count >= adjudicateLossPlies ) {
7440                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7441
7442                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7443                     "Xboard adjudication",
7444                     GE_XBOARD );
7445
7446                 return;
7447             }
7448         }
7449
7450         if(Adjudicate(cps)) {
7451             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7452             return; // [HGM] adjudicate: for all automatic game ends
7453         }
7454
7455 #if ZIPPY
7456         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7457             first.initDone) {
7458           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7459                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7460                 SendToICS("draw ");
7461                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7462           }
7463           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7464           ics_user_moved = 1;
7465           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7466                 char buf[3*MSG_SIZ];
7467
7468                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7469                         programStats.score / 100.,
7470                         programStats.depth,
7471                         programStats.time / 100.,
7472                         (unsigned int)programStats.nodes,
7473                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7474                         programStats.movelist);
7475                 SendToICS(buf);
7476 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7477           }
7478         }
7479 #endif
7480
7481         /* [AS] Clear stats for next move */
7482         ClearProgramStats();
7483         thinkOutput[0] = NULLCHAR;
7484         hiddenThinkOutputState = 0;
7485
7486         bookHit = NULL;
7487         if (gameMode == TwoMachinesPlay) {
7488             /* [HGM] relaying draw offers moved to after reception of move */
7489             /* and interpreting offer as claim if it brings draw condition */
7490             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7491                 SendToProgram("draw\n", cps->other);
7492             }
7493             if (cps->other->sendTime) {
7494                 SendTimeRemaining(cps->other,
7495                                   cps->other->twoMachinesColor[0] == 'w');
7496             }
7497             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7498             if (firstMove && !bookHit) {
7499                 firstMove = FALSE;
7500                 if (cps->other->useColors) {
7501                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7502                 }
7503                 SendToProgram("go\n", cps->other);
7504             }
7505             cps->other->maybeThinking = TRUE;
7506         }
7507
7508         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7509
7510         if (!pausing && appData.ringBellAfterMoves) {
7511             RingBell();
7512         }
7513
7514         /*
7515          * Reenable menu items that were disabled while
7516          * machine was thinking
7517          */
7518         if (gameMode != TwoMachinesPlay)
7519             SetUserThinkingEnables();
7520
7521         // [HGM] book: after book hit opponent has received move and is now in force mode
7522         // force the book reply into it, and then fake that it outputted this move by jumping
7523         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7524         if(bookHit) {
7525                 static char bookMove[MSG_SIZ]; // a bit generous?
7526
7527                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7528                 strcat(bookMove, bookHit);
7529                 message = bookMove;
7530                 cps = cps->other;
7531                 programStats.nodes = programStats.depth = programStats.time =
7532                 programStats.score = programStats.got_only_move = 0;
7533                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7534
7535                 if(cps->lastPing != cps->lastPong) {
7536                     savedMessage = message; // args for deferred call
7537                     savedState = cps;
7538                     ScheduleDelayedEvent(DeferredBookMove, 10);
7539                     return;
7540                 }
7541                 goto FakeBookMove;
7542         }
7543
7544         return;
7545     }
7546
7547     /* Set special modes for chess engines.  Later something general
7548      *  could be added here; for now there is just one kludge feature,
7549      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7550      *  when "xboard" is given as an interactive command.
7551      */
7552     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7553         cps->useSigint = FALSE;
7554         cps->useSigterm = FALSE;
7555     }
7556     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7557       ParseFeatures(message+8, cps);
7558       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7559     }
7560
7561     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7562       int dummy, s=6; char buf[MSG_SIZ];
7563       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7564       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7565       ParseFEN(boards[0], &dummy, message+s);
7566       DrawPosition(TRUE, boards[0]);
7567       startedFromSetupPosition = TRUE;
7568       return;
7569     }
7570     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7571      * want this, I was asked to put it in, and obliged.
7572      */
7573     if (!strncmp(message, "setboard ", 9)) {
7574         Board initial_position;
7575
7576         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7577
7578         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7579             DisplayError(_("Bad FEN received from engine"), 0);
7580             return ;
7581         } else {
7582            Reset(TRUE, FALSE);
7583            CopyBoard(boards[0], initial_position);
7584            initialRulePlies = FENrulePlies;
7585            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7586            else gameMode = MachinePlaysBlack;
7587            DrawPosition(FALSE, boards[currentMove]);
7588         }
7589         return;
7590     }
7591
7592     /*
7593      * Look for communication commands
7594      */
7595     if (!strncmp(message, "telluser ", 9)) {
7596         if(message[9] == '\\' && message[10] == '\\')
7597             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7598         DisplayNote(message + 9);
7599         return;
7600     }
7601     if (!strncmp(message, "tellusererror ", 14)) {
7602         cps->userError = 1;
7603         if(message[14] == '\\' && message[15] == '\\')
7604             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7605         DisplayError(message + 14, 0);
7606         return;
7607     }
7608     if (!strncmp(message, "tellopponent ", 13)) {
7609       if (appData.icsActive) {
7610         if (loggedOn) {
7611           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7612           SendToICS(buf1);
7613         }
7614       } else {
7615         DisplayNote(message + 13);
7616       }
7617       return;
7618     }
7619     if (!strncmp(message, "tellothers ", 11)) {
7620       if (appData.icsActive) {
7621         if (loggedOn) {
7622           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7623           SendToICS(buf1);
7624         }
7625       }
7626       return;
7627     }
7628     if (!strncmp(message, "tellall ", 8)) {
7629       if (appData.icsActive) {
7630         if (loggedOn) {
7631           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7632           SendToICS(buf1);
7633         }
7634       } else {
7635         DisplayNote(message + 8);
7636       }
7637       return;
7638     }
7639     if (strncmp(message, "warning", 7) == 0) {
7640         /* Undocumented feature, use tellusererror in new code */
7641         DisplayError(message, 0);
7642         return;
7643     }
7644     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7645         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7646         strcat(realname, " query");
7647         AskQuestion(realname, buf2, buf1, cps->pr);
7648         return;
7649     }
7650     /* Commands from the engine directly to ICS.  We don't allow these to be
7651      *  sent until we are logged on. Crafty kibitzes have been known to
7652      *  interfere with the login process.
7653      */
7654     if (loggedOn) {
7655         if (!strncmp(message, "tellics ", 8)) {
7656             SendToICS(message + 8);
7657             SendToICS("\n");
7658             return;
7659         }
7660         if (!strncmp(message, "tellicsnoalias ", 15)) {
7661             SendToICS(ics_prefix);
7662             SendToICS(message + 15);
7663             SendToICS("\n");
7664             return;
7665         }
7666         /* The following are for backward compatibility only */
7667         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7668             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7669             SendToICS(ics_prefix);
7670             SendToICS(message);
7671             SendToICS("\n");
7672             return;
7673         }
7674     }
7675     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7676         return;
7677     }
7678     /*
7679      * If the move is illegal, cancel it and redraw the board.
7680      * Also deal with other error cases.  Matching is rather loose
7681      * here to accommodate engines written before the spec.
7682      */
7683     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7684         strncmp(message, "Error", 5) == 0) {
7685         if (StrStr(message, "name") ||
7686             StrStr(message, "rating") || StrStr(message, "?") ||
7687             StrStr(message, "result") || StrStr(message, "board") ||
7688             StrStr(message, "bk") || StrStr(message, "computer") ||
7689             StrStr(message, "variant") || StrStr(message, "hint") ||
7690             StrStr(message, "random") || StrStr(message, "depth") ||
7691             StrStr(message, "accepted")) {
7692             return;
7693         }
7694         if (StrStr(message, "protover")) {
7695           /* Program is responding to input, so it's apparently done
7696              initializing, and this error message indicates it is
7697              protocol version 1.  So we don't need to wait any longer
7698              for it to initialize and send feature commands. */
7699           FeatureDone(cps, 1);
7700           cps->protocolVersion = 1;
7701           return;
7702         }
7703         cps->maybeThinking = FALSE;
7704
7705         if (StrStr(message, "draw")) {
7706             /* Program doesn't have "draw" command */
7707             cps->sendDrawOffers = 0;
7708             return;
7709         }
7710         if (cps->sendTime != 1 &&
7711             (StrStr(message, "time") || StrStr(message, "otim"))) {
7712           /* Program apparently doesn't have "time" or "otim" command */
7713           cps->sendTime = 0;
7714           return;
7715         }
7716         if (StrStr(message, "analyze")) {
7717             cps->analysisSupport = FALSE;
7718             cps->analyzing = FALSE;
7719             Reset(FALSE, TRUE);
7720             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7721             DisplayError(buf2, 0);
7722             return;
7723         }
7724         if (StrStr(message, "(no matching move)st")) {
7725           /* Special kludge for GNU Chess 4 only */
7726           cps->stKludge = TRUE;
7727           SendTimeControl(cps, movesPerSession, timeControl,
7728                           timeIncrement, appData.searchDepth,
7729                           searchTime);
7730           return;
7731         }
7732         if (StrStr(message, "(no matching move)sd")) {
7733           /* Special kludge for GNU Chess 4 only */
7734           cps->sdKludge = TRUE;
7735           SendTimeControl(cps, movesPerSession, timeControl,
7736                           timeIncrement, appData.searchDepth,
7737                           searchTime);
7738           return;
7739         }
7740         if (!StrStr(message, "llegal")) {
7741             return;
7742         }
7743         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7744             gameMode == IcsIdle) return;
7745         if (forwardMostMove <= backwardMostMove) return;
7746         if (pausing) PauseEvent();
7747       if(appData.forceIllegal) {
7748             // [HGM] illegal: machine refused move; force position after move into it
7749           SendToProgram("force\n", cps);
7750           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7751                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7752                 // when black is to move, while there might be nothing on a2 or black
7753                 // might already have the move. So send the board as if white has the move.
7754                 // But first we must change the stm of the engine, as it refused the last move
7755                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7756                 if(WhiteOnMove(forwardMostMove)) {
7757                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7758                     SendBoard(cps, forwardMostMove); // kludgeless board
7759                 } else {
7760                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7761                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7762                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7763                 }
7764           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7765             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7766                  gameMode == TwoMachinesPlay)
7767               SendToProgram("go\n", cps);
7768             return;
7769       } else
7770         if (gameMode == PlayFromGameFile) {
7771             /* Stop reading this game file */
7772             gameMode = EditGame;
7773             ModeHighlight();
7774         }
7775         /* [HGM] illegal-move claim should forfeit game when Xboard */
7776         /* only passes fully legal moves                            */
7777         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7778             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7779                                 "False illegal-move claim", GE_XBOARD );
7780             return; // do not take back move we tested as valid
7781         }
7782         currentMove = forwardMostMove-1;
7783         DisplayMove(currentMove-1); /* before DisplayMoveError */
7784         SwitchClocks(forwardMostMove-1); // [HGM] race
7785         DisplayBothClocks();
7786         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7787                 parseList[currentMove], _(cps->which));
7788         DisplayMoveError(buf1);
7789         DrawPosition(FALSE, boards[currentMove]);
7790         return;
7791     }
7792     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7793         /* Program has a broken "time" command that
7794            outputs a string not ending in newline.
7795            Don't use it. */
7796         cps->sendTime = 0;
7797     }
7798
7799     /*
7800      * If chess program startup fails, exit with an error message.
7801      * Attempts to recover here are futile.
7802      */
7803     if ((StrStr(message, "unknown host") != NULL)
7804         || (StrStr(message, "No remote directory") != NULL)
7805         || (StrStr(message, "not found") != NULL)
7806         || (StrStr(message, "No such file") != NULL)
7807         || (StrStr(message, "can't alloc") != NULL)
7808         || (StrStr(message, "Permission denied") != NULL)) {
7809
7810         cps->maybeThinking = FALSE;
7811         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7812                 _(cps->which), cps->program, cps->host, message);
7813         RemoveInputSource(cps->isr);
7814         DisplayFatalError(buf1, 0, 1);
7815         return;
7816     }
7817
7818     /*
7819      * Look for hint output
7820      */
7821     if (sscanf(message, "Hint: %s", buf1) == 1) {
7822         if (cps == &first && hintRequested) {
7823             hintRequested = FALSE;
7824             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7825                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7826                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7827                                     PosFlags(forwardMostMove),
7828                                     fromY, fromX, toY, toX, promoChar, buf1);
7829                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7830                 DisplayInformation(buf2);
7831             } else {
7832                 /* Hint move could not be parsed!? */
7833               snprintf(buf2, sizeof(buf2),
7834                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7835                         buf1, _(cps->which));
7836                 DisplayError(buf2, 0);
7837             }
7838         } else {
7839           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7840         }
7841         return;
7842     }
7843
7844     /*
7845      * Ignore other messages if game is not in progress
7846      */
7847     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7848         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7849
7850     /*
7851      * look for win, lose, draw, or draw offer
7852      */
7853     if (strncmp(message, "1-0", 3) == 0) {
7854         char *p, *q, *r = "";
7855         p = strchr(message, '{');
7856         if (p) {
7857             q = strchr(p, '}');
7858             if (q) {
7859                 *q = NULLCHAR;
7860                 r = p + 1;
7861             }
7862         }
7863         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7864         return;
7865     } else if (strncmp(message, "0-1", 3) == 0) {
7866         char *p, *q, *r = "";
7867         p = strchr(message, '{');
7868         if (p) {
7869             q = strchr(p, '}');
7870             if (q) {
7871                 *q = NULLCHAR;
7872                 r = p + 1;
7873             }
7874         }
7875         /* Kludge for Arasan 4.1 bug */
7876         if (strcmp(r, "Black resigns") == 0) {
7877             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7878             return;
7879         }
7880         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7881         return;
7882     } else if (strncmp(message, "1/2", 3) == 0) {
7883         char *p, *q, *r = "";
7884         p = strchr(message, '{');
7885         if (p) {
7886             q = strchr(p, '}');
7887             if (q) {
7888                 *q = NULLCHAR;
7889                 r = p + 1;
7890             }
7891         }
7892
7893         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7894         return;
7895
7896     } else if (strncmp(message, "White resign", 12) == 0) {
7897         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7898         return;
7899     } else if (strncmp(message, "Black resign", 12) == 0) {
7900         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7901         return;
7902     } else if (strncmp(message, "White matches", 13) == 0 ||
7903                strncmp(message, "Black matches", 13) == 0   ) {
7904         /* [HGM] ignore GNUShogi noises */
7905         return;
7906     } else if (strncmp(message, "White", 5) == 0 &&
7907                message[5] != '(' &&
7908                StrStr(message, "Black") == NULL) {
7909         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7910         return;
7911     } else if (strncmp(message, "Black", 5) == 0 &&
7912                message[5] != '(') {
7913         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7914         return;
7915     } else if (strcmp(message, "resign") == 0 ||
7916                strcmp(message, "computer resigns") == 0) {
7917         switch (gameMode) {
7918           case MachinePlaysBlack:
7919           case IcsPlayingBlack:
7920             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7921             break;
7922           case MachinePlaysWhite:
7923           case IcsPlayingWhite:
7924             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7925             break;
7926           case TwoMachinesPlay:
7927             if (cps->twoMachinesColor[0] == 'w')
7928               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7929             else
7930               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7931             break;
7932           default:
7933             /* can't happen */
7934             break;
7935         }
7936         return;
7937     } else if (strncmp(message, "opponent mates", 14) == 0) {
7938         switch (gameMode) {
7939           case MachinePlaysBlack:
7940           case IcsPlayingBlack:
7941             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7942             break;
7943           case MachinePlaysWhite:
7944           case IcsPlayingWhite:
7945             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7946             break;
7947           case TwoMachinesPlay:
7948             if (cps->twoMachinesColor[0] == 'w')
7949               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7950             else
7951               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7952             break;
7953           default:
7954             /* can't happen */
7955             break;
7956         }
7957         return;
7958     } else if (strncmp(message, "computer mates", 14) == 0) {
7959         switch (gameMode) {
7960           case MachinePlaysBlack:
7961           case IcsPlayingBlack:
7962             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7963             break;
7964           case MachinePlaysWhite:
7965           case IcsPlayingWhite:
7966             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7967             break;
7968           case TwoMachinesPlay:
7969             if (cps->twoMachinesColor[0] == 'w')
7970               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7971             else
7972               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7973             break;
7974           default:
7975             /* can't happen */
7976             break;
7977         }
7978         return;
7979     } else if (strncmp(message, "checkmate", 9) == 0) {
7980         if (WhiteOnMove(forwardMostMove)) {
7981             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7982         } else {
7983             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7984         }
7985         return;
7986     } else if (strstr(message, "Draw") != NULL ||
7987                strstr(message, "game is a draw") != NULL) {
7988         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7989         return;
7990     } else if (strstr(message, "offer") != NULL &&
7991                strstr(message, "draw") != NULL) {
7992 #if ZIPPY
7993         if (appData.zippyPlay && first.initDone) {
7994             /* Relay offer to ICS */
7995             SendToICS(ics_prefix);
7996             SendToICS("draw\n");
7997         }
7998 #endif
7999         cps->offeredDraw = 2; /* valid until this engine moves twice */
8000         if (gameMode == TwoMachinesPlay) {
8001             if (cps->other->offeredDraw) {
8002                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8003             /* [HGM] in two-machine mode we delay relaying draw offer      */
8004             /* until after we also have move, to see if it is really claim */
8005             }
8006         } else if (gameMode == MachinePlaysWhite ||
8007                    gameMode == MachinePlaysBlack) {
8008           if (userOfferedDraw) {
8009             DisplayInformation(_("Machine accepts your draw offer"));
8010             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8011           } else {
8012             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8013           }
8014         }
8015     }
8016
8017
8018     /*
8019      * Look for thinking output
8020      */
8021     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8022           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8023                                 ) {
8024         int plylev, mvleft, mvtot, curscore, time;
8025         char mvname[MOVE_LEN];
8026         u64 nodes; // [DM]
8027         char plyext;
8028         int ignore = FALSE;
8029         int prefixHint = FALSE;
8030         mvname[0] = NULLCHAR;
8031
8032         switch (gameMode) {
8033           case MachinePlaysBlack:
8034           case IcsPlayingBlack:
8035             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8036             break;
8037           case MachinePlaysWhite:
8038           case IcsPlayingWhite:
8039             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8040             break;
8041           case AnalyzeMode:
8042           case AnalyzeFile:
8043             break;
8044           case IcsObserving: /* [DM] icsEngineAnalyze */
8045             if (!appData.icsEngineAnalyze) ignore = TRUE;
8046             break;
8047           case TwoMachinesPlay:
8048             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8049                 ignore = TRUE;
8050             }
8051             break;
8052           default:
8053             ignore = TRUE;
8054             break;
8055         }
8056
8057         if (!ignore) {
8058             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8059             buf1[0] = NULLCHAR;
8060             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8061                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8062
8063                 if (plyext != ' ' && plyext != '\t') {
8064                     time *= 100;
8065                 }
8066
8067                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8068                 if( cps->scoreIsAbsolute &&
8069                     ( gameMode == MachinePlaysBlack ||
8070                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8071                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8072                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8073                      !WhiteOnMove(currentMove)
8074                     ) )
8075                 {
8076                     curscore = -curscore;
8077                 }
8078
8079
8080                 tempStats.depth = plylev;
8081                 tempStats.nodes = nodes;
8082                 tempStats.time = time;
8083                 tempStats.score = curscore;
8084                 tempStats.got_only_move = 0;
8085
8086                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8087                         int ticklen;
8088
8089                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8090                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8091                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8092                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8093                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8094                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8095                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8096                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8097                 }
8098
8099                 /* Buffer overflow protection */
8100                 if (buf1[0] != NULLCHAR) {
8101                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8102                         && appData.debugMode) {
8103                         fprintf(debugFP,
8104                                 "PV is too long; using the first %u bytes.\n",
8105                                 (unsigned) sizeof(tempStats.movelist) - 1);
8106                     }
8107
8108                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8109                 } else {
8110                     sprintf(tempStats.movelist, " no PV\n");
8111                 }
8112
8113                 if (tempStats.seen_stat) {
8114                     tempStats.ok_to_send = 1;
8115                 }
8116
8117                 if (strchr(tempStats.movelist, '(') != NULL) {
8118                     tempStats.line_is_book = 1;
8119                     tempStats.nr_moves = 0;
8120                     tempStats.moves_left = 0;
8121                 } else {
8122                     tempStats.line_is_book = 0;
8123                 }
8124
8125                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8126                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8127
8128                 SendProgramStatsToFrontend( cps, &tempStats );
8129
8130                 /*
8131                     [AS] Protect the thinkOutput buffer from overflow... this
8132                     is only useful if buf1 hasn't overflowed first!
8133                 */
8134                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8135                          plylev,
8136                          (gameMode == TwoMachinesPlay ?
8137                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8138                          ((double) curscore) / 100.0,
8139                          prefixHint ? lastHint : "",
8140                          prefixHint ? " " : "" );
8141
8142                 if( buf1[0] != NULLCHAR ) {
8143                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8144
8145                     if( strlen(buf1) > max_len ) {
8146                         if( appData.debugMode) {
8147                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8148                         }
8149                         buf1[max_len+1] = '\0';
8150                     }
8151
8152                     strcat( thinkOutput, buf1 );
8153                 }
8154
8155                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8156                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8157                     DisplayMove(currentMove - 1);
8158                 }
8159                 return;
8160
8161             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8162                 /* crafty (9.25+) says "(only move) <move>"
8163                  * if there is only 1 legal move
8164                  */
8165                 sscanf(p, "(only move) %s", buf1);
8166                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8167                 sprintf(programStats.movelist, "%s (only move)", buf1);
8168                 programStats.depth = 1;
8169                 programStats.nr_moves = 1;
8170                 programStats.moves_left = 1;
8171                 programStats.nodes = 1;
8172                 programStats.time = 1;
8173                 programStats.got_only_move = 1;
8174
8175                 /* Not really, but we also use this member to
8176                    mean "line isn't going to change" (Crafty
8177                    isn't searching, so stats won't change) */
8178                 programStats.line_is_book = 1;
8179
8180                 SendProgramStatsToFrontend( cps, &programStats );
8181
8182                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8183                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8184                     DisplayMove(currentMove - 1);
8185                 }
8186                 return;
8187             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8188                               &time, &nodes, &plylev, &mvleft,
8189                               &mvtot, mvname) >= 5) {
8190                 /* The stat01: line is from Crafty (9.29+) in response
8191                    to the "." command */
8192                 programStats.seen_stat = 1;
8193                 cps->maybeThinking = TRUE;
8194
8195                 if (programStats.got_only_move || !appData.periodicUpdates)
8196                   return;
8197
8198                 programStats.depth = plylev;
8199                 programStats.time = time;
8200                 programStats.nodes = nodes;
8201                 programStats.moves_left = mvleft;
8202                 programStats.nr_moves = mvtot;
8203                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8204                 programStats.ok_to_send = 1;
8205                 programStats.movelist[0] = '\0';
8206
8207                 SendProgramStatsToFrontend( cps, &programStats );
8208
8209                 return;
8210
8211             } else if (strncmp(message,"++",2) == 0) {
8212                 /* Crafty 9.29+ outputs this */
8213                 programStats.got_fail = 2;
8214                 return;
8215
8216             } else if (strncmp(message,"--",2) == 0) {
8217                 /* Crafty 9.29+ outputs this */
8218                 programStats.got_fail = 1;
8219                 return;
8220
8221             } else if (thinkOutput[0] != NULLCHAR &&
8222                        strncmp(message, "    ", 4) == 0) {
8223                 unsigned message_len;
8224
8225                 p = message;
8226                 while (*p && *p == ' ') p++;
8227
8228                 message_len = strlen( p );
8229
8230                 /* [AS] Avoid buffer overflow */
8231                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8232                     strcat(thinkOutput, " ");
8233                     strcat(thinkOutput, p);
8234                 }
8235
8236                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8237                     strcat(programStats.movelist, " ");
8238                     strcat(programStats.movelist, p);
8239                 }
8240
8241                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8242                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8243                     DisplayMove(currentMove - 1);
8244                 }
8245                 return;
8246             }
8247         }
8248         else {
8249             buf1[0] = NULLCHAR;
8250
8251             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8252                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8253             {
8254                 ChessProgramStats cpstats;
8255
8256                 if (plyext != ' ' && plyext != '\t') {
8257                     time *= 100;
8258                 }
8259
8260                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8261                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8262                     curscore = -curscore;
8263                 }
8264
8265                 cpstats.depth = plylev;
8266                 cpstats.nodes = nodes;
8267                 cpstats.time = time;
8268                 cpstats.score = curscore;
8269                 cpstats.got_only_move = 0;
8270                 cpstats.movelist[0] = '\0';
8271
8272                 if (buf1[0] != NULLCHAR) {
8273                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8274                 }
8275
8276                 cpstats.ok_to_send = 0;
8277                 cpstats.line_is_book = 0;
8278                 cpstats.nr_moves = 0;
8279                 cpstats.moves_left = 0;
8280
8281                 SendProgramStatsToFrontend( cps, &cpstats );
8282             }
8283         }
8284     }
8285 }
8286
8287
8288 /* Parse a game score from the character string "game", and
8289    record it as the history of the current game.  The game
8290    score is NOT assumed to start from the standard position.
8291    The display is not updated in any way.
8292    */
8293 void
8294 ParseGameHistory(game)
8295      char *game;
8296 {
8297     ChessMove moveType;
8298     int fromX, fromY, toX, toY, boardIndex;
8299     char promoChar;
8300     char *p, *q;
8301     char buf[MSG_SIZ];
8302
8303     if (appData.debugMode)
8304       fprintf(debugFP, "Parsing game history: %s\n", game);
8305
8306     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8307     gameInfo.site = StrSave(appData.icsHost);
8308     gameInfo.date = PGNDate();
8309     gameInfo.round = StrSave("-");
8310
8311     /* Parse out names of players */
8312     while (*game == ' ') game++;
8313     p = buf;
8314     while (*game != ' ') *p++ = *game++;
8315     *p = NULLCHAR;
8316     gameInfo.white = StrSave(buf);
8317     while (*game == ' ') game++;
8318     p = buf;
8319     while (*game != ' ' && *game != '\n') *p++ = *game++;
8320     *p = NULLCHAR;
8321     gameInfo.black = StrSave(buf);
8322
8323     /* Parse moves */
8324     boardIndex = blackPlaysFirst ? 1 : 0;
8325     yynewstr(game);
8326     for (;;) {
8327         yyboardindex = boardIndex;
8328         moveType = (ChessMove) Myylex();
8329         switch (moveType) {
8330           case IllegalMove:             /* maybe suicide chess, etc. */
8331   if (appData.debugMode) {
8332     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8333     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8334     setbuf(debugFP, NULL);
8335   }
8336           case WhitePromotion:
8337           case BlackPromotion:
8338           case WhiteNonPromotion:
8339           case BlackNonPromotion:
8340           case NormalMove:
8341           case WhiteCapturesEnPassant:
8342           case BlackCapturesEnPassant:
8343           case WhiteKingSideCastle:
8344           case WhiteQueenSideCastle:
8345           case BlackKingSideCastle:
8346           case BlackQueenSideCastle:
8347           case WhiteKingSideCastleWild:
8348           case WhiteQueenSideCastleWild:
8349           case BlackKingSideCastleWild:
8350           case BlackQueenSideCastleWild:
8351           /* PUSH Fabien */
8352           case WhiteHSideCastleFR:
8353           case WhiteASideCastleFR:
8354           case BlackHSideCastleFR:
8355           case BlackASideCastleFR:
8356           /* POP Fabien */
8357             fromX = currentMoveString[0] - AAA;
8358             fromY = currentMoveString[1] - ONE;
8359             toX = currentMoveString[2] - AAA;
8360             toY = currentMoveString[3] - ONE;
8361             promoChar = currentMoveString[4];
8362             break;
8363           case WhiteDrop:
8364           case BlackDrop:
8365             fromX = moveType == WhiteDrop ?
8366               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8367             (int) CharToPiece(ToLower(currentMoveString[0]));
8368             fromY = DROP_RANK;
8369             toX = currentMoveString[2] - AAA;
8370             toY = currentMoveString[3] - ONE;
8371             promoChar = NULLCHAR;
8372             break;
8373           case AmbiguousMove:
8374             /* bug? */
8375             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8376   if (appData.debugMode) {
8377     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8378     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8379     setbuf(debugFP, NULL);
8380   }
8381             DisplayError(buf, 0);
8382             return;
8383           case ImpossibleMove:
8384             /* bug? */
8385             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8386   if (appData.debugMode) {
8387     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8388     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8389     setbuf(debugFP, NULL);
8390   }
8391             DisplayError(buf, 0);
8392             return;
8393           case EndOfFile:
8394             if (boardIndex < backwardMostMove) {
8395                 /* Oops, gap.  How did that happen? */
8396                 DisplayError(_("Gap in move list"), 0);
8397                 return;
8398             }
8399             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8400             if (boardIndex > forwardMostMove) {
8401                 forwardMostMove = boardIndex;
8402             }
8403             return;
8404           case ElapsedTime:
8405             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8406                 strcat(parseList[boardIndex-1], " ");
8407                 strcat(parseList[boardIndex-1], yy_text);
8408             }
8409             continue;
8410           case Comment:
8411           case PGNTag:
8412           case NAG:
8413           default:
8414             /* ignore */
8415             continue;
8416           case WhiteWins:
8417           case BlackWins:
8418           case GameIsDrawn:
8419           case GameUnfinished:
8420             if (gameMode == IcsExamining) {
8421                 if (boardIndex < backwardMostMove) {
8422                     /* Oops, gap.  How did that happen? */
8423                     return;
8424                 }
8425                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8426                 return;
8427             }
8428             gameInfo.result = moveType;
8429             p = strchr(yy_text, '{');
8430             if (p == NULL) p = strchr(yy_text, '(');
8431             if (p == NULL) {
8432                 p = yy_text;
8433                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8434             } else {
8435                 q = strchr(p, *p == '{' ? '}' : ')');
8436                 if (q != NULL) *q = NULLCHAR;
8437                 p++;
8438             }
8439             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8440             gameInfo.resultDetails = StrSave(p);
8441             continue;
8442         }
8443         if (boardIndex >= forwardMostMove &&
8444             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8445             backwardMostMove = blackPlaysFirst ? 1 : 0;
8446             return;
8447         }
8448         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8449                                  fromY, fromX, toY, toX, promoChar,
8450                                  parseList[boardIndex]);
8451         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8452         /* currentMoveString is set as a side-effect of yylex */
8453         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8454         strcat(moveList[boardIndex], "\n");
8455         boardIndex++;
8456         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8457         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8458           case MT_NONE:
8459           case MT_STALEMATE:
8460           default:
8461             break;
8462           case MT_CHECK:
8463             if(gameInfo.variant != VariantShogi)
8464                 strcat(parseList[boardIndex - 1], "+");
8465             break;
8466           case MT_CHECKMATE:
8467           case MT_STAINMATE:
8468             strcat(parseList[boardIndex - 1], "#");
8469             break;
8470         }
8471     }
8472 }
8473
8474
8475 /* Apply a move to the given board  */
8476 void
8477 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8478      int fromX, fromY, toX, toY;
8479      int promoChar;
8480      Board board;
8481 {
8482   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8483   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8484
8485     /* [HGM] compute & store e.p. status and castling rights for new position */
8486     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8487
8488       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8489       oldEP = (signed char)board[EP_STATUS];
8490       board[EP_STATUS] = EP_NONE;
8491
8492       if( board[toY][toX] != EmptySquare )
8493            board[EP_STATUS] = EP_CAPTURE;
8494
8495   if (fromY == DROP_RANK) {
8496         /* must be first */
8497         piece = board[toY][toX] = (ChessSquare) fromX;
8498   } else {
8499       int i;
8500
8501       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8502            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8503                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8504       } else
8505       if( board[fromY][fromX] == WhitePawn ) {
8506            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8507                board[EP_STATUS] = EP_PAWN_MOVE;
8508            if( toY-fromY==2) {
8509                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8510                         gameInfo.variant != VariantBerolina || toX < fromX)
8511                       board[EP_STATUS] = toX | berolina;
8512                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8513                         gameInfo.variant != VariantBerolina || toX > fromX)
8514                       board[EP_STATUS] = toX;
8515            }
8516       } else
8517       if( board[fromY][fromX] == BlackPawn ) {
8518            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8519                board[EP_STATUS] = EP_PAWN_MOVE;
8520            if( toY-fromY== -2) {
8521                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8522                         gameInfo.variant != VariantBerolina || toX < fromX)
8523                       board[EP_STATUS] = toX | berolina;
8524                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8525                         gameInfo.variant != VariantBerolina || toX > fromX)
8526                       board[EP_STATUS] = toX;
8527            }
8528        }
8529
8530        for(i=0; i<nrCastlingRights; i++) {
8531            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8532               board[CASTLING][i] == toX   && castlingRank[i] == toY
8533              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8534        }
8535
8536      if (fromX == toX && fromY == toY) return;
8537
8538      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8539      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8540      if(gameInfo.variant == VariantKnightmate)
8541          king += (int) WhiteUnicorn - (int) WhiteKing;
8542
8543     /* Code added by Tord: */
8544     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8545     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8546         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8547       board[fromY][fromX] = EmptySquare;
8548       board[toY][toX] = EmptySquare;
8549       if((toX > fromX) != (piece == WhiteRook)) {
8550         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8551       } else {
8552         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8553       }
8554     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8555                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8556       board[fromY][fromX] = EmptySquare;
8557       board[toY][toX] = EmptySquare;
8558       if((toX > fromX) != (piece == BlackRook)) {
8559         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8560       } else {
8561         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8562       }
8563     /* End of code added by Tord */
8564
8565     } else if (board[fromY][fromX] == king
8566         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8567         && toY == fromY && toX > fromX+1) {
8568         board[fromY][fromX] = EmptySquare;
8569         board[toY][toX] = king;
8570         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8571         board[fromY][BOARD_RGHT-1] = EmptySquare;
8572     } else if (board[fromY][fromX] == king
8573         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8574                && toY == fromY && toX < fromX-1) {
8575         board[fromY][fromX] = EmptySquare;
8576         board[toY][toX] = king;
8577         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8578         board[fromY][BOARD_LEFT] = EmptySquare;
8579     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8580                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8581                && toY >= BOARD_HEIGHT-promoRank
8582                ) {
8583         /* white pawn promotion */
8584         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8585         if (board[toY][toX] == EmptySquare) {
8586             board[toY][toX] = WhiteQueen;
8587         }
8588         if(gameInfo.variant==VariantBughouse ||
8589            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8590             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8591         board[fromY][fromX] = EmptySquare;
8592     } else if ((fromY == BOARD_HEIGHT-4)
8593                && (toX != fromX)
8594                && gameInfo.variant != VariantXiangqi
8595                && gameInfo.variant != VariantBerolina
8596                && (board[fromY][fromX] == WhitePawn)
8597                && (board[toY][toX] == EmptySquare)) {
8598         board[fromY][fromX] = EmptySquare;
8599         board[toY][toX] = WhitePawn;
8600         captured = board[toY - 1][toX];
8601         board[toY - 1][toX] = EmptySquare;
8602     } else if ((fromY == BOARD_HEIGHT-4)
8603                && (toX == fromX)
8604                && gameInfo.variant == VariantBerolina
8605                && (board[fromY][fromX] == WhitePawn)
8606                && (board[toY][toX] == EmptySquare)) {
8607         board[fromY][fromX] = EmptySquare;
8608         board[toY][toX] = WhitePawn;
8609         if(oldEP & EP_BEROLIN_A) {
8610                 captured = board[fromY][fromX-1];
8611                 board[fromY][fromX-1] = EmptySquare;
8612         }else{  captured = board[fromY][fromX+1];
8613                 board[fromY][fromX+1] = EmptySquare;
8614         }
8615     } else if (board[fromY][fromX] == king
8616         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8617                && toY == fromY && toX > fromX+1) {
8618         board[fromY][fromX] = EmptySquare;
8619         board[toY][toX] = king;
8620         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8621         board[fromY][BOARD_RGHT-1] = EmptySquare;
8622     } else if (board[fromY][fromX] == king
8623         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8624                && toY == fromY && toX < fromX-1) {
8625         board[fromY][fromX] = EmptySquare;
8626         board[toY][toX] = king;
8627         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8628         board[fromY][BOARD_LEFT] = EmptySquare;
8629     } else if (fromY == 7 && fromX == 3
8630                && board[fromY][fromX] == BlackKing
8631                && toY == 7 && toX == 5) {
8632         board[fromY][fromX] = EmptySquare;
8633         board[toY][toX] = BlackKing;
8634         board[fromY][7] = EmptySquare;
8635         board[toY][4] = BlackRook;
8636     } else if (fromY == 7 && fromX == 3
8637                && board[fromY][fromX] == BlackKing
8638                && toY == 7 && toX == 1) {
8639         board[fromY][fromX] = EmptySquare;
8640         board[toY][toX] = BlackKing;
8641         board[fromY][0] = EmptySquare;
8642         board[toY][2] = BlackRook;
8643     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8644                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8645                && toY < promoRank
8646                ) {
8647         /* black pawn promotion */
8648         board[toY][toX] = CharToPiece(ToLower(promoChar));
8649         if (board[toY][toX] == EmptySquare) {
8650             board[toY][toX] = BlackQueen;
8651         }
8652         if(gameInfo.variant==VariantBughouse ||
8653            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8654             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8655         board[fromY][fromX] = EmptySquare;
8656     } else if ((fromY == 3)
8657                && (toX != fromX)
8658                && gameInfo.variant != VariantXiangqi
8659                && gameInfo.variant != VariantBerolina
8660                && (board[fromY][fromX] == BlackPawn)
8661                && (board[toY][toX] == EmptySquare)) {
8662         board[fromY][fromX] = EmptySquare;
8663         board[toY][toX] = BlackPawn;
8664         captured = board[toY + 1][toX];
8665         board[toY + 1][toX] = EmptySquare;
8666     } else if ((fromY == 3)
8667                && (toX == fromX)
8668                && gameInfo.variant == VariantBerolina
8669                && (board[fromY][fromX] == BlackPawn)
8670                && (board[toY][toX] == EmptySquare)) {
8671         board[fromY][fromX] = EmptySquare;
8672         board[toY][toX] = BlackPawn;
8673         if(oldEP & EP_BEROLIN_A) {
8674                 captured = board[fromY][fromX-1];
8675                 board[fromY][fromX-1] = EmptySquare;
8676         }else{  captured = board[fromY][fromX+1];
8677                 board[fromY][fromX+1] = EmptySquare;
8678         }
8679     } else {
8680         board[toY][toX] = board[fromY][fromX];
8681         board[fromY][fromX] = EmptySquare;
8682     }
8683   }
8684
8685     if (gameInfo.holdingsWidth != 0) {
8686
8687       /* !!A lot more code needs to be written to support holdings  */
8688       /* [HGM] OK, so I have written it. Holdings are stored in the */
8689       /* penultimate board files, so they are automaticlly stored   */
8690       /* in the game history.                                       */
8691       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8692                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8693         /* Delete from holdings, by decreasing count */
8694         /* and erasing image if necessary            */
8695         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8696         if(p < (int) BlackPawn) { /* white drop */
8697              p -= (int)WhitePawn;
8698                  p = PieceToNumber((ChessSquare)p);
8699              if(p >= gameInfo.holdingsSize) p = 0;
8700              if(--board[p][BOARD_WIDTH-2] <= 0)
8701                   board[p][BOARD_WIDTH-1] = EmptySquare;
8702              if((int)board[p][BOARD_WIDTH-2] < 0)
8703                         board[p][BOARD_WIDTH-2] = 0;
8704         } else {                  /* black drop */
8705              p -= (int)BlackPawn;
8706                  p = PieceToNumber((ChessSquare)p);
8707              if(p >= gameInfo.holdingsSize) p = 0;
8708              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8709                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8710              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8711                         board[BOARD_HEIGHT-1-p][1] = 0;
8712         }
8713       }
8714       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8715           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8716         /* [HGM] holdings: Add to holdings, if holdings exist */
8717         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8718                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8719                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8720         }
8721         p = (int) captured;
8722         if (p >= (int) BlackPawn) {
8723           p -= (int)BlackPawn;
8724           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8725                   /* in Shogi restore piece to its original  first */
8726                   captured = (ChessSquare) (DEMOTED captured);
8727                   p = DEMOTED p;
8728           }
8729           p = PieceToNumber((ChessSquare)p);
8730           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8731           board[p][BOARD_WIDTH-2]++;
8732           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8733         } else {
8734           p -= (int)WhitePawn;
8735           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8736                   captured = (ChessSquare) (DEMOTED captured);
8737                   p = DEMOTED p;
8738           }
8739           p = PieceToNumber((ChessSquare)p);
8740           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8741           board[BOARD_HEIGHT-1-p][1]++;
8742           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8743         }
8744       }
8745     } else if (gameInfo.variant == VariantAtomic) {
8746       if (captured != EmptySquare) {
8747         int y, x;
8748         for (y = toY-1; y <= toY+1; y++) {
8749           for (x = toX-1; x <= toX+1; x++) {
8750             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8751                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8752               board[y][x] = EmptySquare;
8753             }
8754           }
8755         }
8756         board[toY][toX] = EmptySquare;
8757       }
8758     }
8759     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8760         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8761     } else
8762     if(promoChar == '+') {
8763         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8764         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8765     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8766         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8767     }
8768     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8769                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8770         // [HGM] superchess: take promotion piece out of holdings
8771         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8772         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8773             if(!--board[k][BOARD_WIDTH-2])
8774                 board[k][BOARD_WIDTH-1] = EmptySquare;
8775         } else {
8776             if(!--board[BOARD_HEIGHT-1-k][1])
8777                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8778         }
8779     }
8780
8781 }
8782
8783 /* Updates forwardMostMove */
8784 void
8785 MakeMove(fromX, fromY, toX, toY, promoChar)
8786      int fromX, fromY, toX, toY;
8787      int promoChar;
8788 {
8789 //    forwardMostMove++; // [HGM] bare: moved downstream
8790
8791     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8792         int timeLeft; static int lastLoadFlag=0; int king, piece;
8793         piece = boards[forwardMostMove][fromY][fromX];
8794         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8795         if(gameInfo.variant == VariantKnightmate)
8796             king += (int) WhiteUnicorn - (int) WhiteKing;
8797         if(forwardMostMove == 0) {
8798             if(blackPlaysFirst)
8799                 fprintf(serverMoves, "%s;", second.tidy);
8800             fprintf(serverMoves, "%s;", first.tidy);
8801             if(!blackPlaysFirst)
8802                 fprintf(serverMoves, "%s;", second.tidy);
8803         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8804         lastLoadFlag = loadFlag;
8805         // print base move
8806         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8807         // print castling suffix
8808         if( toY == fromY && piece == king ) {
8809             if(toX-fromX > 1)
8810                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8811             if(fromX-toX >1)
8812                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8813         }
8814         // e.p. suffix
8815         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8816              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8817              boards[forwardMostMove][toY][toX] == EmptySquare
8818              && fromX != toX && fromY != toY)
8819                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8820         // promotion suffix
8821         if(promoChar != NULLCHAR)
8822                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8823         if(!loadFlag) {
8824             fprintf(serverMoves, "/%d/%d",
8825                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8826             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8827             else                      timeLeft = blackTimeRemaining/1000;
8828             fprintf(serverMoves, "/%d", timeLeft);
8829         }
8830         fflush(serverMoves);
8831     }
8832
8833     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8834       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8835                         0, 1);
8836       return;
8837     }
8838     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8839     if (commentList[forwardMostMove+1] != NULL) {
8840         free(commentList[forwardMostMove+1]);
8841         commentList[forwardMostMove+1] = NULL;
8842     }
8843     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8844     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8845     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8846     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8847     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8848     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8849     gameInfo.result = GameUnfinished;
8850     if (gameInfo.resultDetails != NULL) {
8851         free(gameInfo.resultDetails);
8852         gameInfo.resultDetails = NULL;
8853     }
8854     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8855                               moveList[forwardMostMove - 1]);
8856     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8857                              PosFlags(forwardMostMove - 1),
8858                              fromY, fromX, toY, toX, promoChar,
8859                              parseList[forwardMostMove - 1]);
8860     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8861       case MT_NONE:
8862       case MT_STALEMATE:
8863       default:
8864         break;
8865       case MT_CHECK:
8866         if(gameInfo.variant != VariantShogi)
8867             strcat(parseList[forwardMostMove - 1], "+");
8868         break;
8869       case MT_CHECKMATE:
8870       case MT_STAINMATE:
8871         strcat(parseList[forwardMostMove - 1], "#");
8872         break;
8873     }
8874     if (appData.debugMode) {
8875         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8876     }
8877
8878 }
8879
8880 /* Updates currentMove if not pausing */
8881 void
8882 ShowMove(fromX, fromY, toX, toY)
8883 {
8884     int instant = (gameMode == PlayFromGameFile) ?
8885         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8886     if(appData.noGUI) return;
8887     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8888         if (!instant) {
8889             if (forwardMostMove == currentMove + 1) {
8890                 AnimateMove(boards[forwardMostMove - 1],
8891                             fromX, fromY, toX, toY);
8892             }
8893             if (appData.highlightLastMove) {
8894                 SetHighlights(fromX, fromY, toX, toY);
8895             }
8896         }
8897         currentMove = forwardMostMove;
8898     }
8899
8900     if (instant) return;
8901
8902     DisplayMove(currentMove - 1);
8903     DrawPosition(FALSE, boards[currentMove]);
8904     DisplayBothClocks();
8905     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8906 }
8907
8908 void SendEgtPath(ChessProgramState *cps)
8909 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8910         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8911
8912         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8913
8914         while(*p) {
8915             char c, *q = name+1, *r, *s;
8916
8917             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8918             while(*p && *p != ',') *q++ = *p++;
8919             *q++ = ':'; *q = 0;
8920             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8921                 strcmp(name, ",nalimov:") == 0 ) {
8922                 // take nalimov path from the menu-changeable option first, if it is defined
8923               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8924                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8925             } else
8926             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8927                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8928                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8929                 s = r = StrStr(s, ":") + 1; // beginning of path info
8930                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8931                 c = *r; *r = 0;             // temporarily null-terminate path info
8932                     *--q = 0;               // strip of trailig ':' from name
8933                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8934                 *r = c;
8935                 SendToProgram(buf,cps);     // send egtbpath command for this format
8936             }
8937             if(*p == ',') p++; // read away comma to position for next format name
8938         }
8939 }
8940
8941 void
8942 InitChessProgram(cps, setup)
8943      ChessProgramState *cps;
8944      int setup; /* [HGM] needed to setup FRC opening position */
8945 {
8946     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8947     if (appData.noChessProgram) return;
8948     hintRequested = FALSE;
8949     bookRequested = FALSE;
8950
8951     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8952     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8953     if(cps->memSize) { /* [HGM] memory */
8954       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8955         SendToProgram(buf, cps);
8956     }
8957     SendEgtPath(cps); /* [HGM] EGT */
8958     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8959       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8960         SendToProgram(buf, cps);
8961     }
8962
8963     SendToProgram(cps->initString, cps);
8964     if (gameInfo.variant != VariantNormal &&
8965         gameInfo.variant != VariantLoadable
8966         /* [HGM] also send variant if board size non-standard */
8967         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8968                                             ) {
8969       char *v = VariantName(gameInfo.variant);
8970       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8971         /* [HGM] in protocol 1 we have to assume all variants valid */
8972         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8973         DisplayFatalError(buf, 0, 1);
8974         return;
8975       }
8976
8977       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8978       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8979       if( gameInfo.variant == VariantXiangqi )
8980            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8981       if( gameInfo.variant == VariantShogi )
8982            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8983       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8984            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8985       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8986           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
8987            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8988       if( gameInfo.variant == VariantCourier )
8989            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8990       if( gameInfo.variant == VariantSuper )
8991            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8992       if( gameInfo.variant == VariantGreat )
8993            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8994       if( gameInfo.variant == VariantSChess )
8995            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8996
8997       if(overruled) {
8998         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8999                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9000            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9001            if(StrStr(cps->variants, b) == NULL) {
9002                // specific sized variant not known, check if general sizing allowed
9003                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9004                    if(StrStr(cps->variants, "boardsize") == NULL) {
9005                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9006                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9007                        DisplayFatalError(buf, 0, 1);
9008                        return;
9009                    }
9010                    /* [HGM] here we really should compare with the maximum supported board size */
9011                }
9012            }
9013       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9014       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9015       SendToProgram(buf, cps);
9016     }
9017     currentlyInitializedVariant = gameInfo.variant;
9018
9019     /* [HGM] send opening position in FRC to first engine */
9020     if(setup) {
9021           SendToProgram("force\n", cps);
9022           SendBoard(cps, 0);
9023           /* engine is now in force mode! Set flag to wake it up after first move. */
9024           setboardSpoiledMachineBlack = 1;
9025     }
9026
9027     if (cps->sendICS) {
9028       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9029       SendToProgram(buf, cps);
9030     }
9031     cps->maybeThinking = FALSE;
9032     cps->offeredDraw = 0;
9033     if (!appData.icsActive) {
9034         SendTimeControl(cps, movesPerSession, timeControl,
9035                         timeIncrement, appData.searchDepth,
9036                         searchTime);
9037     }
9038     if (appData.showThinking
9039         // [HGM] thinking: four options require thinking output to be sent
9040         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9041                                 ) {
9042         SendToProgram("post\n", cps);
9043     }
9044     SendToProgram("hard\n", cps);
9045     if (!appData.ponderNextMove) {
9046         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9047            it without being sure what state we are in first.  "hard"
9048            is not a toggle, so that one is OK.
9049          */
9050         SendToProgram("easy\n", cps);
9051     }
9052     if (cps->usePing) {
9053       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9054       SendToProgram(buf, cps);
9055     }
9056     cps->initDone = TRUE;
9057 }
9058
9059
9060 void
9061 StartChessProgram(cps)
9062      ChessProgramState *cps;
9063 {
9064     char buf[MSG_SIZ];
9065     int err;
9066
9067     if (appData.noChessProgram) return;
9068     cps->initDone = FALSE;
9069
9070     if (strcmp(cps->host, "localhost") == 0) {
9071         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9072     } else if (*appData.remoteShell == NULLCHAR) {
9073         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9074     } else {
9075         if (*appData.remoteUser == NULLCHAR) {
9076           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9077                     cps->program);
9078         } else {
9079           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9080                     cps->host, appData.remoteUser, cps->program);
9081         }
9082         err = StartChildProcess(buf, "", &cps->pr);
9083     }
9084
9085     if (err != 0) {
9086       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9087         DisplayFatalError(buf, err, 1);
9088         cps->pr = NoProc;
9089         cps->isr = NULL;
9090         return;
9091     }
9092
9093     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9094     if (cps->protocolVersion > 1) {
9095       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9096       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9097       cps->comboCnt = 0;  //                and values of combo boxes
9098       SendToProgram(buf, cps);
9099     } else {
9100       SendToProgram("xboard\n", cps);
9101     }
9102 }
9103
9104
9105 void
9106 TwoMachinesEventIfReady P((void))
9107 {
9108   if (first.lastPing != first.lastPong) {
9109     DisplayMessage("", _("Waiting for first chess program"));
9110     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9111     return;
9112   }
9113   if (second.lastPing != second.lastPong) {
9114     DisplayMessage("", _("Waiting for second chess program"));
9115     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9116     return;
9117   }
9118   ThawUI();
9119   TwoMachinesEvent();
9120 }
9121
9122 void
9123 NextMatchGame P((void))
9124 {
9125     int index; /* [HGM] autoinc: step load index during match */
9126     Reset(FALSE, TRUE);
9127     if (*appData.loadGameFile != NULLCHAR) {
9128         index = appData.loadGameIndex;
9129         if(index < 0) { // [HGM] autoinc
9130             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9131             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9132         }
9133         LoadGameFromFile(appData.loadGameFile,
9134                          index,
9135                          appData.loadGameFile, FALSE);
9136     } else if (*appData.loadPositionFile != NULLCHAR) {
9137         index = appData.loadPositionIndex;
9138         if(index < 0) { // [HGM] autoinc
9139             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9140             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9141         }
9142         LoadPositionFromFile(appData.loadPositionFile,
9143                              index,
9144                              appData.loadPositionFile);
9145     }
9146     TwoMachinesEventIfReady();
9147 }
9148
9149 void UserAdjudicationEvent( int result )
9150 {
9151     ChessMove gameResult = GameIsDrawn;
9152
9153     if( result > 0 ) {
9154         gameResult = WhiteWins;
9155     }
9156     else if( result < 0 ) {
9157         gameResult = BlackWins;
9158     }
9159
9160     if( gameMode == TwoMachinesPlay ) {
9161         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9162     }
9163 }
9164
9165
9166 // [HGM] save: calculate checksum of game to make games easily identifiable
9167 int StringCheckSum(char *s)
9168 {
9169         int i = 0;
9170         if(s==NULL) return 0;
9171         while(*s) i = i*259 + *s++;
9172         return i;
9173 }
9174
9175 int GameCheckSum()
9176 {
9177         int i, sum=0;
9178         for(i=backwardMostMove; i<forwardMostMove; i++) {
9179                 sum += pvInfoList[i].depth;
9180                 sum += StringCheckSum(parseList[i]);
9181                 sum += StringCheckSum(commentList[i]);
9182                 sum *= 261;
9183         }
9184         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9185         return sum + StringCheckSum(commentList[i]);
9186 } // end of save patch
9187
9188 void
9189 GameEnds(result, resultDetails, whosays)
9190      ChessMove result;
9191      char *resultDetails;
9192      int whosays;
9193 {
9194     GameMode nextGameMode;
9195     int isIcsGame;
9196     char buf[MSG_SIZ], popupRequested = 0;
9197
9198     if(endingGame) return; /* [HGM] crash: forbid recursion */
9199     endingGame = 1;
9200     if(twoBoards) { // [HGM] dual: switch back to one board
9201         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9202         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9203     }
9204     if (appData.debugMode) {
9205       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9206               result, resultDetails ? resultDetails : "(null)", whosays);
9207     }
9208
9209     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9210
9211     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9212         /* If we are playing on ICS, the server decides when the
9213            game is over, but the engine can offer to draw, claim
9214            a draw, or resign.
9215          */
9216 #if ZIPPY
9217         if (appData.zippyPlay && first.initDone) {
9218             if (result == GameIsDrawn) {
9219                 /* In case draw still needs to be claimed */
9220                 SendToICS(ics_prefix);
9221                 SendToICS("draw\n");
9222             } else if (StrCaseStr(resultDetails, "resign")) {
9223                 SendToICS(ics_prefix);
9224                 SendToICS("resign\n");
9225             }
9226         }
9227 #endif
9228         endingGame = 0; /* [HGM] crash */
9229         return;
9230     }
9231
9232     /* If we're loading the game from a file, stop */
9233     if (whosays == GE_FILE) {
9234       (void) StopLoadGameTimer();
9235       gameFileFP = NULL;
9236     }
9237
9238     /* Cancel draw offers */
9239     first.offeredDraw = second.offeredDraw = 0;
9240
9241     /* If this is an ICS game, only ICS can really say it's done;
9242        if not, anyone can. */
9243     isIcsGame = (gameMode == IcsPlayingWhite ||
9244                  gameMode == IcsPlayingBlack ||
9245                  gameMode == IcsObserving    ||
9246                  gameMode == IcsExamining);
9247
9248     if (!isIcsGame || whosays == GE_ICS) {
9249         /* OK -- not an ICS game, or ICS said it was done */
9250         StopClocks();
9251         if (!isIcsGame && !appData.noChessProgram)
9252           SetUserThinkingEnables();
9253
9254         /* [HGM] if a machine claims the game end we verify this claim */
9255         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9256             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9257                 char claimer;
9258                 ChessMove trueResult = (ChessMove) -1;
9259
9260                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9261                                             first.twoMachinesColor[0] :
9262                                             second.twoMachinesColor[0] ;
9263
9264                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9265                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9266                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9267                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9268                 } else
9269                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9270                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9271                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9272                 } else
9273                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9274                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9275                 }
9276
9277                 // now verify win claims, but not in drop games, as we don't understand those yet
9278                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9279                                                  || gameInfo.variant == VariantGreat) &&
9280                     (result == WhiteWins && claimer == 'w' ||
9281                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9282                       if (appData.debugMode) {
9283                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9284                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9285                       }
9286                       if(result != trueResult) {
9287                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9288                               result = claimer == 'w' ? BlackWins : WhiteWins;
9289                               resultDetails = buf;
9290                       }
9291                 } else
9292                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9293                     && (forwardMostMove <= backwardMostMove ||
9294                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9295                         (claimer=='b')==(forwardMostMove&1))
9296                                                                                   ) {
9297                       /* [HGM] verify: draws that were not flagged are false claims */
9298                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9299                       result = claimer == 'w' ? BlackWins : WhiteWins;
9300                       resultDetails = buf;
9301                 }
9302                 /* (Claiming a loss is accepted no questions asked!) */
9303             }
9304             /* [HGM] bare: don't allow bare King to win */
9305             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9306                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9307                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9308                && result != GameIsDrawn)
9309             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9310                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9311                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9312                         if(p >= 0 && p <= (int)WhiteKing) k++;
9313                 }
9314                 if (appData.debugMode) {
9315                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9316                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9317                 }
9318                 if(k <= 1) {
9319                         result = GameIsDrawn;
9320                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9321                         resultDetails = buf;
9322                 }
9323             }
9324         }
9325
9326
9327         if(serverMoves != NULL && !loadFlag) { char c = '=';
9328             if(result==WhiteWins) c = '+';
9329             if(result==BlackWins) c = '-';
9330             if(resultDetails != NULL)
9331                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9332         }
9333         if (resultDetails != NULL) {
9334             gameInfo.result = result;
9335             gameInfo.resultDetails = StrSave(resultDetails);
9336
9337             /* display last move only if game was not loaded from file */
9338             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9339                 DisplayMove(currentMove - 1);
9340
9341             if (forwardMostMove != 0) {
9342                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9343                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9344                                                                 ) {
9345                     if (*appData.saveGameFile != NULLCHAR) {
9346                         SaveGameToFile(appData.saveGameFile, TRUE);
9347                     } else if (appData.autoSaveGames) {
9348                         AutoSaveGame();
9349                     }
9350                     if (*appData.savePositionFile != NULLCHAR) {
9351                         SavePositionToFile(appData.savePositionFile);
9352                     }
9353                 }
9354             }
9355
9356             /* Tell program how game ended in case it is learning */
9357             /* [HGM] Moved this to after saving the PGN, just in case */
9358             /* engine died and we got here through time loss. In that */
9359             /* case we will get a fatal error writing the pipe, which */
9360             /* would otherwise lose us the PGN.                       */
9361             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9362             /* output during GameEnds should never be fatal anymore   */
9363             if (gameMode == MachinePlaysWhite ||
9364                 gameMode == MachinePlaysBlack ||
9365                 gameMode == TwoMachinesPlay ||
9366                 gameMode == IcsPlayingWhite ||
9367                 gameMode == IcsPlayingBlack ||
9368                 gameMode == BeginningOfGame) {
9369                 char buf[MSG_SIZ];
9370                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9371                         resultDetails);
9372                 if (first.pr != NoProc) {
9373                     SendToProgram(buf, &first);
9374                 }
9375                 if (second.pr != NoProc &&
9376                     gameMode == TwoMachinesPlay) {
9377                     SendToProgram(buf, &second);
9378                 }
9379             }
9380         }
9381
9382         if (appData.icsActive) {
9383             if (appData.quietPlay &&
9384                 (gameMode == IcsPlayingWhite ||
9385                  gameMode == IcsPlayingBlack)) {
9386                 SendToICS(ics_prefix);
9387                 SendToICS("set shout 1\n");
9388             }
9389             nextGameMode = IcsIdle;
9390             ics_user_moved = FALSE;
9391             /* clean up premove.  It's ugly when the game has ended and the
9392              * premove highlights are still on the board.
9393              */
9394             if (gotPremove) {
9395               gotPremove = FALSE;
9396               ClearPremoveHighlights();
9397               DrawPosition(FALSE, boards[currentMove]);
9398             }
9399             if (whosays == GE_ICS) {
9400                 switch (result) {
9401                 case WhiteWins:
9402                     if (gameMode == IcsPlayingWhite)
9403                         PlayIcsWinSound();
9404                     else if(gameMode == IcsPlayingBlack)
9405                         PlayIcsLossSound();
9406                     break;
9407                 case BlackWins:
9408                     if (gameMode == IcsPlayingBlack)
9409                         PlayIcsWinSound();
9410                     else if(gameMode == IcsPlayingWhite)
9411                         PlayIcsLossSound();
9412                     break;
9413                 case GameIsDrawn:
9414                     PlayIcsDrawSound();
9415                     break;
9416                 default:
9417                     PlayIcsUnfinishedSound();
9418                 }
9419             }
9420         } else if (gameMode == EditGame ||
9421                    gameMode == PlayFromGameFile ||
9422                    gameMode == AnalyzeMode ||
9423                    gameMode == AnalyzeFile) {
9424             nextGameMode = gameMode;
9425         } else {
9426             nextGameMode = EndOfGame;
9427         }
9428         pausing = FALSE;
9429         ModeHighlight();
9430     } else {
9431         nextGameMode = gameMode;
9432     }
9433
9434     if (appData.noChessProgram) {
9435         gameMode = nextGameMode;
9436         ModeHighlight();
9437         endingGame = 0; /* [HGM] crash */
9438         return;
9439     }
9440
9441     if (first.reuse) {
9442         /* Put first chess program into idle state */
9443         if (first.pr != NoProc &&
9444             (gameMode == MachinePlaysWhite ||
9445              gameMode == MachinePlaysBlack ||
9446              gameMode == TwoMachinesPlay ||
9447              gameMode == IcsPlayingWhite ||
9448              gameMode == IcsPlayingBlack ||
9449              gameMode == BeginningOfGame)) {
9450             SendToProgram("force\n", &first);
9451             if (first.usePing) {
9452               char buf[MSG_SIZ];
9453               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9454               SendToProgram(buf, &first);
9455             }
9456         }
9457     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9458         /* Kill off first chess program */
9459         if (first.isr != NULL)
9460           RemoveInputSource(first.isr);
9461         first.isr = NULL;
9462
9463         if (first.pr != NoProc) {
9464             ExitAnalyzeMode();
9465             DoSleep( appData.delayBeforeQuit );
9466             SendToProgram("quit\n", &first);
9467             DoSleep( appData.delayAfterQuit );
9468             DestroyChildProcess(first.pr, first.useSigterm);
9469         }
9470         first.pr = NoProc;
9471     }
9472     if (second.reuse) {
9473         /* Put second chess program into idle state */
9474         if (second.pr != NoProc &&
9475             gameMode == TwoMachinesPlay) {
9476             SendToProgram("force\n", &second);
9477             if (second.usePing) {
9478               char buf[MSG_SIZ];
9479               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9480               SendToProgram(buf, &second);
9481             }
9482         }
9483     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9484         /* Kill off second chess program */
9485         if (second.isr != NULL)
9486           RemoveInputSource(second.isr);
9487         second.isr = NULL;
9488
9489         if (second.pr != NoProc) {
9490             DoSleep( appData.delayBeforeQuit );
9491             SendToProgram("quit\n", &second);
9492             DoSleep( appData.delayAfterQuit );
9493             DestroyChildProcess(second.pr, second.useSigterm);
9494         }
9495         second.pr = NoProc;
9496     }
9497
9498     if (matchMode && gameMode == TwoMachinesPlay) {
9499         switch (result) {
9500         case WhiteWins:
9501           if (first.twoMachinesColor[0] == 'w') {
9502             first.matchWins++;
9503           } else {
9504             second.matchWins++;
9505           }
9506           break;
9507         case BlackWins:
9508           if (first.twoMachinesColor[0] == 'b') {
9509             first.matchWins++;
9510           } else {
9511             second.matchWins++;
9512           }
9513           break;
9514         default:
9515           break;
9516         }
9517         if (matchGame < appData.matchGames) {
9518             char *tmp;
9519             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9520                 tmp = first.twoMachinesColor;
9521                 first.twoMachinesColor = second.twoMachinesColor;
9522                 second.twoMachinesColor = tmp;
9523             }
9524             gameMode = nextGameMode;
9525             matchGame++;
9526             if(appData.matchPause>10000 || appData.matchPause<10)
9527                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9528             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9529             endingGame = 0; /* [HGM] crash */
9530             return;
9531         } else {
9532             gameMode = nextGameMode;
9533             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9534                      first.tidy, second.tidy,
9535                      first.matchWins, second.matchWins,
9536                      appData.matchGames - (first.matchWins + second.matchWins));
9537             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9538             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9539                 first.twoMachinesColor = "black\n";
9540                 second.twoMachinesColor = "white\n";
9541             } else {
9542                 first.twoMachinesColor = "white\n";
9543                 second.twoMachinesColor = "black\n";
9544             }
9545         }
9546     }
9547     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9548         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9549       ExitAnalyzeMode();
9550     gameMode = nextGameMode;
9551     ModeHighlight();
9552     endingGame = 0;  /* [HGM] crash */
9553     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9554       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9555         matchMode = FALSE; appData.matchGames = matchGame = 0;
9556         DisplayNote(buf);
9557       }
9558     }
9559 }
9560
9561 /* Assumes program was just initialized (initString sent).
9562    Leaves program in force mode. */
9563 void
9564 FeedMovesToProgram(cps, upto)
9565      ChessProgramState *cps;
9566      int upto;
9567 {
9568     int i;
9569
9570     if (appData.debugMode)
9571       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9572               startedFromSetupPosition ? "position and " : "",
9573               backwardMostMove, upto, cps->which);
9574     if(currentlyInitializedVariant != gameInfo.variant) {
9575       char buf[MSG_SIZ];
9576         // [HGM] variantswitch: make engine aware of new variant
9577         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9578                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9579         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9580         SendToProgram(buf, cps);
9581         currentlyInitializedVariant = gameInfo.variant;
9582     }
9583     SendToProgram("force\n", cps);
9584     if (startedFromSetupPosition) {
9585         SendBoard(cps, backwardMostMove);
9586     if (appData.debugMode) {
9587         fprintf(debugFP, "feedMoves\n");
9588     }
9589     }
9590     for (i = backwardMostMove; i < upto; i++) {
9591         SendMoveToProgram(i, cps);
9592     }
9593 }
9594
9595
9596 void
9597 ResurrectChessProgram()
9598 {
9599      /* The chess program may have exited.
9600         If so, restart it and feed it all the moves made so far. */
9601
9602     if (appData.noChessProgram || first.pr != NoProc) return;
9603
9604     StartChessProgram(&first);
9605     InitChessProgram(&first, FALSE);
9606     FeedMovesToProgram(&first, currentMove);
9607
9608     if (!first.sendTime) {
9609         /* can't tell gnuchess what its clock should read,
9610            so we bow to its notion. */
9611         ResetClocks();
9612         timeRemaining[0][currentMove] = whiteTimeRemaining;
9613         timeRemaining[1][currentMove] = blackTimeRemaining;
9614     }
9615
9616     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9617                 appData.icsEngineAnalyze) && first.analysisSupport) {
9618       SendToProgram("analyze\n", &first);
9619       first.analyzing = TRUE;
9620     }
9621 }
9622
9623 /*
9624  * Button procedures
9625  */
9626 void
9627 Reset(redraw, init)
9628      int redraw, init;
9629 {
9630     int i;
9631
9632     if (appData.debugMode) {
9633         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9634                 redraw, init, gameMode);
9635     }
9636     CleanupTail(); // [HGM] vari: delete any stored variations
9637     pausing = pauseExamInvalid = FALSE;
9638     startedFromSetupPosition = blackPlaysFirst = FALSE;
9639     firstMove = TRUE;
9640     whiteFlag = blackFlag = FALSE;
9641     userOfferedDraw = FALSE;
9642     hintRequested = bookRequested = FALSE;
9643     first.maybeThinking = FALSE;
9644     second.maybeThinking = FALSE;
9645     first.bookSuspend = FALSE; // [HGM] book
9646     second.bookSuspend = FALSE;
9647     thinkOutput[0] = NULLCHAR;
9648     lastHint[0] = NULLCHAR;
9649     ClearGameInfo(&gameInfo);
9650     gameInfo.variant = StringToVariant(appData.variant);
9651     ics_user_moved = ics_clock_paused = FALSE;
9652     ics_getting_history = H_FALSE;
9653     ics_gamenum = -1;
9654     white_holding[0] = black_holding[0] = NULLCHAR;
9655     ClearProgramStats();
9656     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9657
9658     ResetFrontEnd();
9659     ClearHighlights();
9660     flipView = appData.flipView;
9661     ClearPremoveHighlights();
9662     gotPremove = FALSE;
9663     alarmSounded = FALSE;
9664
9665     GameEnds(EndOfFile, NULL, GE_PLAYER);
9666     if(appData.serverMovesName != NULL) {
9667         /* [HGM] prepare to make moves file for broadcasting */
9668         clock_t t = clock();
9669         if(serverMoves != NULL) fclose(serverMoves);
9670         serverMoves = fopen(appData.serverMovesName, "r");
9671         if(serverMoves != NULL) {
9672             fclose(serverMoves);
9673             /* delay 15 sec before overwriting, so all clients can see end */
9674             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9675         }
9676         serverMoves = fopen(appData.serverMovesName, "w");
9677     }
9678
9679     ExitAnalyzeMode();
9680     gameMode = BeginningOfGame;
9681     ModeHighlight();
9682     if(appData.icsActive) gameInfo.variant = VariantNormal;
9683     currentMove = forwardMostMove = backwardMostMove = 0;
9684     InitPosition(redraw);
9685     for (i = 0; i < MAX_MOVES; i++) {
9686         if (commentList[i] != NULL) {
9687             free(commentList[i]);
9688             commentList[i] = NULL;
9689         }
9690     }
9691     ResetClocks();
9692     timeRemaining[0][0] = whiteTimeRemaining;
9693     timeRemaining[1][0] = blackTimeRemaining;
9694     if (first.pr == NULL) {
9695         StartChessProgram(&first);
9696     }
9697     if (init) {
9698             InitChessProgram(&first, startedFromSetupPosition);
9699     }
9700     DisplayTitle("");
9701     DisplayMessage("", "");
9702     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9703     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9704 }
9705
9706 void
9707 AutoPlayGameLoop()
9708 {
9709     for (;;) {
9710         if (!AutoPlayOneMove())
9711           return;
9712         if (matchMode || appData.timeDelay == 0)
9713           continue;
9714         if (appData.timeDelay < 0)
9715           return;
9716         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9717         break;
9718     }
9719 }
9720
9721
9722 int
9723 AutoPlayOneMove()
9724 {
9725     int fromX, fromY, toX, toY;
9726
9727     if (appData.debugMode) {
9728       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9729     }
9730
9731     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9732       return FALSE;
9733
9734     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9735       pvInfoList[currentMove].depth = programStats.depth;
9736       pvInfoList[currentMove].score = programStats.score;
9737       pvInfoList[currentMove].time  = 0;
9738       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9739     }
9740
9741     if (currentMove >= forwardMostMove) {
9742       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9743       gameMode = EditGame;
9744       ModeHighlight();
9745
9746       /* [AS] Clear current move marker at the end of a game */
9747       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9748
9749       return FALSE;
9750     }
9751
9752     toX = moveList[currentMove][2] - AAA;
9753     toY = moveList[currentMove][3] - ONE;
9754
9755     if (moveList[currentMove][1] == '@') {
9756         if (appData.highlightLastMove) {
9757             SetHighlights(-1, -1, toX, toY);
9758         }
9759     } else {
9760         fromX = moveList[currentMove][0] - AAA;
9761         fromY = moveList[currentMove][1] - ONE;
9762
9763         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9764
9765         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9766
9767         if (appData.highlightLastMove) {
9768             SetHighlights(fromX, fromY, toX, toY);
9769         }
9770     }
9771     DisplayMove(currentMove);
9772     SendMoveToProgram(currentMove++, &first);
9773     DisplayBothClocks();
9774     DrawPosition(FALSE, boards[currentMove]);
9775     // [HGM] PV info: always display, routine tests if empty
9776     DisplayComment(currentMove - 1, commentList[currentMove]);
9777     return TRUE;
9778 }
9779
9780
9781 int
9782 LoadGameOneMove(readAhead)
9783      ChessMove readAhead;
9784 {
9785     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9786     char promoChar = NULLCHAR;
9787     ChessMove moveType;
9788     char move[MSG_SIZ];
9789     char *p, *q;
9790
9791     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9792         gameMode != AnalyzeMode && gameMode != Training) {
9793         gameFileFP = NULL;
9794         return FALSE;
9795     }
9796
9797     yyboardindex = forwardMostMove;
9798     if (readAhead != EndOfFile) {
9799       moveType = readAhead;
9800     } else {
9801       if (gameFileFP == NULL)
9802           return FALSE;
9803       moveType = (ChessMove) Myylex();
9804     }
9805
9806     done = FALSE;
9807     switch (moveType) {
9808       case Comment:
9809         if (appData.debugMode)
9810           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9811         p = yy_text;
9812
9813         /* append the comment but don't display it */
9814         AppendComment(currentMove, p, FALSE);
9815         return TRUE;
9816
9817       case WhiteCapturesEnPassant:
9818       case BlackCapturesEnPassant:
9819       case WhitePromotion:
9820       case BlackPromotion:
9821       case WhiteNonPromotion:
9822       case BlackNonPromotion:
9823       case NormalMove:
9824       case WhiteKingSideCastle:
9825       case WhiteQueenSideCastle:
9826       case BlackKingSideCastle:
9827       case BlackQueenSideCastle:
9828       case WhiteKingSideCastleWild:
9829       case WhiteQueenSideCastleWild:
9830       case BlackKingSideCastleWild:
9831       case BlackQueenSideCastleWild:
9832       /* PUSH Fabien */
9833       case WhiteHSideCastleFR:
9834       case WhiteASideCastleFR:
9835       case BlackHSideCastleFR:
9836       case BlackASideCastleFR:
9837       /* POP Fabien */
9838         if (appData.debugMode)
9839           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9840         fromX = currentMoveString[0] - AAA;
9841         fromY = currentMoveString[1] - ONE;
9842         toX = currentMoveString[2] - AAA;
9843         toY = currentMoveString[3] - ONE;
9844         promoChar = currentMoveString[4];
9845         break;
9846
9847       case WhiteDrop:
9848       case BlackDrop:
9849         if (appData.debugMode)
9850           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9851         fromX = moveType == WhiteDrop ?
9852           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9853         (int) CharToPiece(ToLower(currentMoveString[0]));
9854         fromY = DROP_RANK;
9855         toX = currentMoveString[2] - AAA;
9856         toY = currentMoveString[3] - ONE;
9857         break;
9858
9859       case WhiteWins:
9860       case BlackWins:
9861       case GameIsDrawn:
9862       case GameUnfinished:
9863         if (appData.debugMode)
9864           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9865         p = strchr(yy_text, '{');
9866         if (p == NULL) p = strchr(yy_text, '(');
9867         if (p == NULL) {
9868             p = yy_text;
9869             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9870         } else {
9871             q = strchr(p, *p == '{' ? '}' : ')');
9872             if (q != NULL) *q = NULLCHAR;
9873             p++;
9874         }
9875         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9876         GameEnds(moveType, p, GE_FILE);
9877         done = TRUE;
9878         if (cmailMsgLoaded) {
9879             ClearHighlights();
9880             flipView = WhiteOnMove(currentMove);
9881             if (moveType == GameUnfinished) flipView = !flipView;
9882             if (appData.debugMode)
9883               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9884         }
9885         break;
9886
9887       case EndOfFile:
9888         if (appData.debugMode)
9889           fprintf(debugFP, "Parser hit end of file\n");
9890         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9891           case MT_NONE:
9892           case MT_CHECK:
9893             break;
9894           case MT_CHECKMATE:
9895           case MT_STAINMATE:
9896             if (WhiteOnMove(currentMove)) {
9897                 GameEnds(BlackWins, "Black mates", GE_FILE);
9898             } else {
9899                 GameEnds(WhiteWins, "White mates", GE_FILE);
9900             }
9901             break;
9902           case MT_STALEMATE:
9903             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9904             break;
9905         }
9906         done = TRUE;
9907         break;
9908
9909       case MoveNumberOne:
9910         if (lastLoadGameStart == GNUChessGame) {
9911             /* GNUChessGames have numbers, but they aren't move numbers */
9912             if (appData.debugMode)
9913               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9914                       yy_text, (int) moveType);
9915             return LoadGameOneMove(EndOfFile); /* tail recursion */
9916         }
9917         /* else fall thru */
9918
9919       case XBoardGame:
9920       case GNUChessGame:
9921       case PGNTag:
9922         /* Reached start of next game in file */
9923         if (appData.debugMode)
9924           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9925         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9926           case MT_NONE:
9927           case MT_CHECK:
9928             break;
9929           case MT_CHECKMATE:
9930           case MT_STAINMATE:
9931             if (WhiteOnMove(currentMove)) {
9932                 GameEnds(BlackWins, "Black mates", GE_FILE);
9933             } else {
9934                 GameEnds(WhiteWins, "White mates", GE_FILE);
9935             }
9936             break;
9937           case MT_STALEMATE:
9938             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9939             break;
9940         }
9941         done = TRUE;
9942         break;
9943
9944       case PositionDiagram:     /* should not happen; ignore */
9945       case ElapsedTime:         /* ignore */
9946       case NAG:                 /* ignore */
9947         if (appData.debugMode)
9948           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9949                   yy_text, (int) moveType);
9950         return LoadGameOneMove(EndOfFile); /* tail recursion */
9951
9952       case IllegalMove:
9953         if (appData.testLegality) {
9954             if (appData.debugMode)
9955               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9956             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9957                     (forwardMostMove / 2) + 1,
9958                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9959             DisplayError(move, 0);
9960             done = TRUE;
9961         } else {
9962             if (appData.debugMode)
9963               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9964                       yy_text, currentMoveString);
9965             fromX = currentMoveString[0] - AAA;
9966             fromY = currentMoveString[1] - ONE;
9967             toX = currentMoveString[2] - AAA;
9968             toY = currentMoveString[3] - ONE;
9969             promoChar = currentMoveString[4];
9970         }
9971         break;
9972
9973       case AmbiguousMove:
9974         if (appData.debugMode)
9975           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9976         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9977                 (forwardMostMove / 2) + 1,
9978                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9979         DisplayError(move, 0);
9980         done = TRUE;
9981         break;
9982
9983       default:
9984       case ImpossibleMove:
9985         if (appData.debugMode)
9986           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9987         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9988                 (forwardMostMove / 2) + 1,
9989                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9990         DisplayError(move, 0);
9991         done = TRUE;
9992         break;
9993     }
9994
9995     if (done) {
9996         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9997             DrawPosition(FALSE, boards[currentMove]);
9998             DisplayBothClocks();
9999             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10000               DisplayComment(currentMove - 1, commentList[currentMove]);
10001         }
10002         (void) StopLoadGameTimer();
10003         gameFileFP = NULL;
10004         cmailOldMove = forwardMostMove;
10005         return FALSE;
10006     } else {
10007         /* currentMoveString is set as a side-effect of yylex */
10008
10009         thinkOutput[0] = NULLCHAR;
10010         MakeMove(fromX, fromY, toX, toY, promoChar);
10011         currentMove = forwardMostMove;
10012         return TRUE;
10013     }
10014 }
10015
10016 /* Load the nth game from the given file */
10017 int
10018 LoadGameFromFile(filename, n, title, useList)
10019      char *filename;
10020      int n;
10021      char *title;
10022      /*Boolean*/ int useList;
10023 {
10024     FILE *f;
10025     char buf[MSG_SIZ];
10026
10027     if (strcmp(filename, "-") == 0) {
10028         f = stdin;
10029         title = "stdin";
10030     } else {
10031         f = fopen(filename, "rb");
10032         if (f == NULL) {
10033           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10034             DisplayError(buf, errno);
10035             return FALSE;
10036         }
10037     }
10038     if (fseek(f, 0, 0) == -1) {
10039         /* f is not seekable; probably a pipe */
10040         useList = FALSE;
10041     }
10042     if (useList && n == 0) {
10043         int error = GameListBuild(f);
10044         if (error) {
10045             DisplayError(_("Cannot build game list"), error);
10046         } else if (!ListEmpty(&gameList) &&
10047                    ((ListGame *) gameList.tailPred)->number > 1) {
10048             GameListPopUp(f, title);
10049             return TRUE;
10050         }
10051         GameListDestroy();
10052         n = 1;
10053     }
10054     if (n == 0) n = 1;
10055     return LoadGame(f, n, title, FALSE);
10056 }
10057
10058
10059 void
10060 MakeRegisteredMove()
10061 {
10062     int fromX, fromY, toX, toY;
10063     char promoChar;
10064     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10065         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10066           case CMAIL_MOVE:
10067           case CMAIL_DRAW:
10068             if (appData.debugMode)
10069               fprintf(debugFP, "Restoring %s for game %d\n",
10070                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10071
10072             thinkOutput[0] = NULLCHAR;
10073             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10074             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10075             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10076             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10077             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10078             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10079             MakeMove(fromX, fromY, toX, toY, promoChar);
10080             ShowMove(fromX, fromY, toX, toY);
10081
10082             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10083               case MT_NONE:
10084               case MT_CHECK:
10085                 break;
10086
10087               case MT_CHECKMATE:
10088               case MT_STAINMATE:
10089                 if (WhiteOnMove(currentMove)) {
10090                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10091                 } else {
10092                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10093                 }
10094                 break;
10095
10096               case MT_STALEMATE:
10097                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10098                 break;
10099             }
10100
10101             break;
10102
10103           case CMAIL_RESIGN:
10104             if (WhiteOnMove(currentMove)) {
10105                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10106             } else {
10107                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10108             }
10109             break;
10110
10111           case CMAIL_ACCEPT:
10112             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10113             break;
10114
10115           default:
10116             break;
10117         }
10118     }
10119
10120     return;
10121 }
10122
10123 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10124 int
10125 CmailLoadGame(f, gameNumber, title, useList)
10126      FILE *f;
10127      int gameNumber;
10128      char *title;
10129      int useList;
10130 {
10131     int retVal;
10132
10133     if (gameNumber > nCmailGames) {
10134         DisplayError(_("No more games in this message"), 0);
10135         return FALSE;
10136     }
10137     if (f == lastLoadGameFP) {
10138         int offset = gameNumber - lastLoadGameNumber;
10139         if (offset == 0) {
10140             cmailMsg[0] = NULLCHAR;
10141             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10142                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10143                 nCmailMovesRegistered--;
10144             }
10145             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10146             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10147                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10148             }
10149         } else {
10150             if (! RegisterMove()) return FALSE;
10151         }
10152     }
10153
10154     retVal = LoadGame(f, gameNumber, title, useList);
10155
10156     /* Make move registered during previous look at this game, if any */
10157     MakeRegisteredMove();
10158
10159     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10160         commentList[currentMove]
10161           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10162         DisplayComment(currentMove - 1, commentList[currentMove]);
10163     }
10164
10165     return retVal;
10166 }
10167
10168 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10169 int
10170 ReloadGame(offset)
10171      int offset;
10172 {
10173     int gameNumber = lastLoadGameNumber + offset;
10174     if (lastLoadGameFP == NULL) {
10175         DisplayError(_("No game has been loaded yet"), 0);
10176         return FALSE;
10177     }
10178     if (gameNumber <= 0) {
10179         DisplayError(_("Can't back up any further"), 0);
10180         return FALSE;
10181     }
10182     if (cmailMsgLoaded) {
10183         return CmailLoadGame(lastLoadGameFP, gameNumber,
10184                              lastLoadGameTitle, lastLoadGameUseList);
10185     } else {
10186         return LoadGame(lastLoadGameFP, gameNumber,
10187                         lastLoadGameTitle, lastLoadGameUseList);
10188     }
10189 }
10190
10191
10192
10193 /* Load the nth game from open file f */
10194 int
10195 LoadGame(f, gameNumber, title, useList)
10196      FILE *f;
10197      int gameNumber;
10198      char *title;
10199      int useList;
10200 {
10201     ChessMove cm;
10202     char buf[MSG_SIZ];
10203     int gn = gameNumber;
10204     ListGame *lg = NULL;
10205     int numPGNTags = 0;
10206     int err;
10207     GameMode oldGameMode;
10208     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10209
10210     if (appData.debugMode)
10211         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10212
10213     if (gameMode == Training )
10214         SetTrainingModeOff();
10215
10216     oldGameMode = gameMode;
10217     if (gameMode != BeginningOfGame) {
10218       Reset(FALSE, TRUE);
10219     }
10220
10221     gameFileFP = f;
10222     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10223         fclose(lastLoadGameFP);
10224     }
10225
10226     if (useList) {
10227         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10228
10229         if (lg) {
10230             fseek(f, lg->offset, 0);
10231             GameListHighlight(gameNumber);
10232             gn = 1;
10233         }
10234         else {
10235             DisplayError(_("Game number out of range"), 0);
10236             return FALSE;
10237         }
10238     } else {
10239         GameListDestroy();
10240         if (fseek(f, 0, 0) == -1) {
10241             if (f == lastLoadGameFP ?
10242                 gameNumber == lastLoadGameNumber + 1 :
10243                 gameNumber == 1) {
10244                 gn = 1;
10245             } else {
10246                 DisplayError(_("Can't seek on game file"), 0);
10247                 return FALSE;
10248             }
10249         }
10250     }
10251     lastLoadGameFP = f;
10252     lastLoadGameNumber = gameNumber;
10253     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10254     lastLoadGameUseList = useList;
10255
10256     yynewfile(f);
10257
10258     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10259       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10260                 lg->gameInfo.black);
10261             DisplayTitle(buf);
10262     } else if (*title != NULLCHAR) {
10263         if (gameNumber > 1) {
10264           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10265             DisplayTitle(buf);
10266         } else {
10267             DisplayTitle(title);
10268         }
10269     }
10270
10271     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10272         gameMode = PlayFromGameFile;
10273         ModeHighlight();
10274     }
10275
10276     currentMove = forwardMostMove = backwardMostMove = 0;
10277     CopyBoard(boards[0], initialPosition);
10278     StopClocks();
10279
10280     /*
10281      * Skip the first gn-1 games in the file.
10282      * Also skip over anything that precedes an identifiable
10283      * start of game marker, to avoid being confused by
10284      * garbage at the start of the file.  Currently
10285      * recognized start of game markers are the move number "1",
10286      * the pattern "gnuchess .* game", the pattern
10287      * "^[#;%] [^ ]* game file", and a PGN tag block.
10288      * A game that starts with one of the latter two patterns
10289      * will also have a move number 1, possibly
10290      * following a position diagram.
10291      * 5-4-02: Let's try being more lenient and allowing a game to
10292      * start with an unnumbered move.  Does that break anything?
10293      */
10294     cm = lastLoadGameStart = EndOfFile;
10295     while (gn > 0) {
10296         yyboardindex = forwardMostMove;
10297         cm = (ChessMove) Myylex();
10298         switch (cm) {
10299           case EndOfFile:
10300             if (cmailMsgLoaded) {
10301                 nCmailGames = CMAIL_MAX_GAMES - gn;
10302             } else {
10303                 Reset(TRUE, TRUE);
10304                 DisplayError(_("Game not found in file"), 0);
10305             }
10306             return FALSE;
10307
10308           case GNUChessGame:
10309           case XBoardGame:
10310             gn--;
10311             lastLoadGameStart = cm;
10312             break;
10313
10314           case MoveNumberOne:
10315             switch (lastLoadGameStart) {
10316               case GNUChessGame:
10317               case XBoardGame:
10318               case PGNTag:
10319                 break;
10320               case MoveNumberOne:
10321               case EndOfFile:
10322                 gn--;           /* count this game */
10323                 lastLoadGameStart = cm;
10324                 break;
10325               default:
10326                 /* impossible */
10327                 break;
10328             }
10329             break;
10330
10331           case PGNTag:
10332             switch (lastLoadGameStart) {
10333               case GNUChessGame:
10334               case PGNTag:
10335               case MoveNumberOne:
10336               case EndOfFile:
10337                 gn--;           /* count this game */
10338                 lastLoadGameStart = cm;
10339                 break;
10340               case XBoardGame:
10341                 lastLoadGameStart = cm; /* game counted already */
10342                 break;
10343               default:
10344                 /* impossible */
10345                 break;
10346             }
10347             if (gn > 0) {
10348                 do {
10349                     yyboardindex = forwardMostMove;
10350                     cm = (ChessMove) Myylex();
10351                 } while (cm == PGNTag || cm == Comment);
10352             }
10353             break;
10354
10355           case WhiteWins:
10356           case BlackWins:
10357           case GameIsDrawn:
10358             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10359                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10360                     != CMAIL_OLD_RESULT) {
10361                     nCmailResults ++ ;
10362                     cmailResult[  CMAIL_MAX_GAMES
10363                                 - gn - 1] = CMAIL_OLD_RESULT;
10364                 }
10365             }
10366             break;
10367
10368           case NormalMove:
10369             /* Only a NormalMove can be at the start of a game
10370              * without a position diagram. */
10371             if (lastLoadGameStart == EndOfFile ) {
10372               gn--;
10373               lastLoadGameStart = MoveNumberOne;
10374             }
10375             break;
10376
10377           default:
10378             break;
10379         }
10380     }
10381
10382     if (appData.debugMode)
10383       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10384
10385     if (cm == XBoardGame) {
10386         /* Skip any header junk before position diagram and/or move 1 */
10387         for (;;) {
10388             yyboardindex = forwardMostMove;
10389             cm = (ChessMove) Myylex();
10390
10391             if (cm == EndOfFile ||
10392                 cm == GNUChessGame || cm == XBoardGame) {
10393                 /* Empty game; pretend end-of-file and handle later */
10394                 cm = EndOfFile;
10395                 break;
10396             }
10397
10398             if (cm == MoveNumberOne || cm == PositionDiagram ||
10399                 cm == PGNTag || cm == Comment)
10400               break;
10401         }
10402     } else if (cm == GNUChessGame) {
10403         if (gameInfo.event != NULL) {
10404             free(gameInfo.event);
10405         }
10406         gameInfo.event = StrSave(yy_text);
10407     }
10408
10409     startedFromSetupPosition = FALSE;
10410     while (cm == PGNTag) {
10411         if (appData.debugMode)
10412           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10413         err = ParsePGNTag(yy_text, &gameInfo);
10414         if (!err) numPGNTags++;
10415
10416         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10417         if(gameInfo.variant != oldVariant) {
10418             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10419             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10420             InitPosition(TRUE);
10421             oldVariant = gameInfo.variant;
10422             if (appData.debugMode)
10423               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10424         }
10425
10426
10427         if (gameInfo.fen != NULL) {
10428           Board initial_position;
10429           startedFromSetupPosition = TRUE;
10430           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10431             Reset(TRUE, TRUE);
10432             DisplayError(_("Bad FEN position in file"), 0);
10433             return FALSE;
10434           }
10435           CopyBoard(boards[0], initial_position);
10436           if (blackPlaysFirst) {
10437             currentMove = forwardMostMove = backwardMostMove = 1;
10438             CopyBoard(boards[1], initial_position);
10439             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10440             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10441             timeRemaining[0][1] = whiteTimeRemaining;
10442             timeRemaining[1][1] = blackTimeRemaining;
10443             if (commentList[0] != NULL) {
10444               commentList[1] = commentList[0];
10445               commentList[0] = NULL;
10446             }
10447           } else {
10448             currentMove = forwardMostMove = backwardMostMove = 0;
10449           }
10450           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10451           {   int i;
10452               initialRulePlies = FENrulePlies;
10453               for( i=0; i< nrCastlingRights; i++ )
10454                   initialRights[i] = initial_position[CASTLING][i];
10455           }
10456           yyboardindex = forwardMostMove;
10457           free(gameInfo.fen);
10458           gameInfo.fen = NULL;
10459         }
10460
10461         yyboardindex = forwardMostMove;
10462         cm = (ChessMove) Myylex();
10463
10464         /* Handle comments interspersed among the tags */
10465         while (cm == Comment) {
10466             char *p;
10467             if (appData.debugMode)
10468               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10469             p = yy_text;
10470             AppendComment(currentMove, p, FALSE);
10471             yyboardindex = forwardMostMove;
10472             cm = (ChessMove) Myylex();
10473         }
10474     }
10475
10476     /* don't rely on existence of Event tag since if game was
10477      * pasted from clipboard the Event tag may not exist
10478      */
10479     if (numPGNTags > 0){
10480         char *tags;
10481         if (gameInfo.variant == VariantNormal) {
10482           VariantClass v = StringToVariant(gameInfo.event);
10483           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10484           if(v < VariantShogi) gameInfo.variant = v;
10485         }
10486         if (!matchMode) {
10487           if( appData.autoDisplayTags ) {
10488             tags = PGNTags(&gameInfo);
10489             TagsPopUp(tags, CmailMsg());
10490             free(tags);
10491           }
10492         }
10493     } else {
10494         /* Make something up, but don't display it now */
10495         SetGameInfo();
10496         TagsPopDown();
10497     }
10498
10499     if (cm == PositionDiagram) {
10500         int i, j;
10501         char *p;
10502         Board initial_position;
10503
10504         if (appData.debugMode)
10505           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10506
10507         if (!startedFromSetupPosition) {
10508             p = yy_text;
10509             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10510               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10511                 switch (*p) {
10512                   case '{':
10513                   case '[':
10514                   case '-':
10515                   case ' ':
10516                   case '\t':
10517                   case '\n':
10518                   case '\r':
10519                     break;
10520                   default:
10521                     initial_position[i][j++] = CharToPiece(*p);
10522                     break;
10523                 }
10524             while (*p == ' ' || *p == '\t' ||
10525                    *p == '\n' || *p == '\r') p++;
10526
10527             if (strncmp(p, "black", strlen("black"))==0)
10528               blackPlaysFirst = TRUE;
10529             else
10530               blackPlaysFirst = FALSE;
10531             startedFromSetupPosition = TRUE;
10532
10533             CopyBoard(boards[0], initial_position);
10534             if (blackPlaysFirst) {
10535                 currentMove = forwardMostMove = backwardMostMove = 1;
10536                 CopyBoard(boards[1], initial_position);
10537                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10538                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10539                 timeRemaining[0][1] = whiteTimeRemaining;
10540                 timeRemaining[1][1] = blackTimeRemaining;
10541                 if (commentList[0] != NULL) {
10542                     commentList[1] = commentList[0];
10543                     commentList[0] = NULL;
10544                 }
10545             } else {
10546                 currentMove = forwardMostMove = backwardMostMove = 0;
10547             }
10548         }
10549         yyboardindex = forwardMostMove;
10550         cm = (ChessMove) Myylex();
10551     }
10552
10553     if (first.pr == NoProc) {
10554         StartChessProgram(&first);
10555     }
10556     InitChessProgram(&first, FALSE);
10557     SendToProgram("force\n", &first);
10558     if (startedFromSetupPosition) {
10559         SendBoard(&first, forwardMostMove);
10560     if (appData.debugMode) {
10561         fprintf(debugFP, "Load Game\n");
10562     }
10563         DisplayBothClocks();
10564     }
10565
10566     /* [HGM] server: flag to write setup moves in broadcast file as one */
10567     loadFlag = appData.suppressLoadMoves;
10568
10569     while (cm == Comment) {
10570         char *p;
10571         if (appData.debugMode)
10572           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10573         p = yy_text;
10574         AppendComment(currentMove, p, FALSE);
10575         yyboardindex = forwardMostMove;
10576         cm = (ChessMove) Myylex();
10577     }
10578
10579     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10580         cm == WhiteWins || cm == BlackWins ||
10581         cm == GameIsDrawn || cm == GameUnfinished) {
10582         DisplayMessage("", _("No moves in game"));
10583         if (cmailMsgLoaded) {
10584             if (appData.debugMode)
10585               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10586             ClearHighlights();
10587             flipView = FALSE;
10588         }
10589         DrawPosition(FALSE, boards[currentMove]);
10590         DisplayBothClocks();
10591         gameMode = EditGame;
10592         ModeHighlight();
10593         gameFileFP = NULL;
10594         cmailOldMove = 0;
10595         return TRUE;
10596     }
10597
10598     // [HGM] PV info: routine tests if comment empty
10599     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10600         DisplayComment(currentMove - 1, commentList[currentMove]);
10601     }
10602     if (!matchMode && appData.timeDelay != 0)
10603       DrawPosition(FALSE, boards[currentMove]);
10604
10605     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10606       programStats.ok_to_send = 1;
10607     }
10608
10609     /* if the first token after the PGN tags is a move
10610      * and not move number 1, retrieve it from the parser
10611      */
10612     if (cm != MoveNumberOne)
10613         LoadGameOneMove(cm);
10614
10615     /* load the remaining moves from the file */
10616     while (LoadGameOneMove(EndOfFile)) {
10617       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10618       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10619     }
10620
10621     /* rewind to the start of the game */
10622     currentMove = backwardMostMove;
10623
10624     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10625
10626     if (oldGameMode == AnalyzeFile ||
10627         oldGameMode == AnalyzeMode) {
10628       AnalyzeFileEvent();
10629     }
10630
10631     if (matchMode || appData.timeDelay == 0) {
10632       ToEndEvent();
10633       gameMode = EditGame;
10634       ModeHighlight();
10635     } else if (appData.timeDelay > 0) {
10636       AutoPlayGameLoop();
10637     }
10638
10639     if (appData.debugMode)
10640         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10641
10642     loadFlag = 0; /* [HGM] true game starts */
10643     return TRUE;
10644 }
10645
10646 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10647 int
10648 ReloadPosition(offset)
10649      int offset;
10650 {
10651     int positionNumber = lastLoadPositionNumber + offset;
10652     if (lastLoadPositionFP == NULL) {
10653         DisplayError(_("No position has been loaded yet"), 0);
10654         return FALSE;
10655     }
10656     if (positionNumber <= 0) {
10657         DisplayError(_("Can't back up any further"), 0);
10658         return FALSE;
10659     }
10660     return LoadPosition(lastLoadPositionFP, positionNumber,
10661                         lastLoadPositionTitle);
10662 }
10663
10664 /* Load the nth position from the given file */
10665 int
10666 LoadPositionFromFile(filename, n, title)
10667      char *filename;
10668      int n;
10669      char *title;
10670 {
10671     FILE *f;
10672     char buf[MSG_SIZ];
10673
10674     if (strcmp(filename, "-") == 0) {
10675         return LoadPosition(stdin, n, "stdin");
10676     } else {
10677         f = fopen(filename, "rb");
10678         if (f == NULL) {
10679             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10680             DisplayError(buf, errno);
10681             return FALSE;
10682         } else {
10683             return LoadPosition(f, n, title);
10684         }
10685     }
10686 }
10687
10688 /* Load the nth position from the given open file, and close it */
10689 int
10690 LoadPosition(f, positionNumber, title)
10691      FILE *f;
10692      int positionNumber;
10693      char *title;
10694 {
10695     char *p, line[MSG_SIZ];
10696     Board initial_position;
10697     int i, j, fenMode, pn;
10698
10699     if (gameMode == Training )
10700         SetTrainingModeOff();
10701
10702     if (gameMode != BeginningOfGame) {
10703         Reset(FALSE, TRUE);
10704     }
10705     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10706         fclose(lastLoadPositionFP);
10707     }
10708     if (positionNumber == 0) positionNumber = 1;
10709     lastLoadPositionFP = f;
10710     lastLoadPositionNumber = positionNumber;
10711     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10712     if (first.pr == NoProc) {
10713       StartChessProgram(&first);
10714       InitChessProgram(&first, FALSE);
10715     }
10716     pn = positionNumber;
10717     if (positionNumber < 0) {
10718         /* Negative position number means to seek to that byte offset */
10719         if (fseek(f, -positionNumber, 0) == -1) {
10720             DisplayError(_("Can't seek on position file"), 0);
10721             return FALSE;
10722         };
10723         pn = 1;
10724     } else {
10725         if (fseek(f, 0, 0) == -1) {
10726             if (f == lastLoadPositionFP ?
10727                 positionNumber == lastLoadPositionNumber + 1 :
10728                 positionNumber == 1) {
10729                 pn = 1;
10730             } else {
10731                 DisplayError(_("Can't seek on position file"), 0);
10732                 return FALSE;
10733             }
10734         }
10735     }
10736     /* See if this file is FEN or old-style xboard */
10737     if (fgets(line, MSG_SIZ, f) == NULL) {
10738         DisplayError(_("Position not found in file"), 0);
10739         return FALSE;
10740     }
10741     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10742     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10743
10744     if (pn >= 2) {
10745         if (fenMode || line[0] == '#') pn--;
10746         while (pn > 0) {
10747             /* skip positions before number pn */
10748             if (fgets(line, MSG_SIZ, f) == NULL) {
10749                 Reset(TRUE, TRUE);
10750                 DisplayError(_("Position not found in file"), 0);
10751                 return FALSE;
10752             }
10753             if (fenMode || line[0] == '#') pn--;
10754         }
10755     }
10756
10757     if (fenMode) {
10758         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10759             DisplayError(_("Bad FEN position in file"), 0);
10760             return FALSE;
10761         }
10762     } else {
10763         (void) fgets(line, MSG_SIZ, f);
10764         (void) fgets(line, MSG_SIZ, f);
10765
10766         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10767             (void) fgets(line, MSG_SIZ, f);
10768             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10769                 if (*p == ' ')
10770                   continue;
10771                 initial_position[i][j++] = CharToPiece(*p);
10772             }
10773         }
10774
10775         blackPlaysFirst = FALSE;
10776         if (!feof(f)) {
10777             (void) fgets(line, MSG_SIZ, f);
10778             if (strncmp(line, "black", strlen("black"))==0)
10779               blackPlaysFirst = TRUE;
10780         }
10781     }
10782     startedFromSetupPosition = TRUE;
10783
10784     SendToProgram("force\n", &first);
10785     CopyBoard(boards[0], initial_position);
10786     if (blackPlaysFirst) {
10787         currentMove = forwardMostMove = backwardMostMove = 1;
10788         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10789         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10790         CopyBoard(boards[1], initial_position);
10791         DisplayMessage("", _("Black to play"));
10792     } else {
10793         currentMove = forwardMostMove = backwardMostMove = 0;
10794         DisplayMessage("", _("White to play"));
10795     }
10796     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10797     SendBoard(&first, forwardMostMove);
10798     if (appData.debugMode) {
10799 int i, j;
10800   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10801   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10802         fprintf(debugFP, "Load Position\n");
10803     }
10804
10805     if (positionNumber > 1) {
10806       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10807         DisplayTitle(line);
10808     } else {
10809         DisplayTitle(title);
10810     }
10811     gameMode = EditGame;
10812     ModeHighlight();
10813     ResetClocks();
10814     timeRemaining[0][1] = whiteTimeRemaining;
10815     timeRemaining[1][1] = blackTimeRemaining;
10816     DrawPosition(FALSE, boards[currentMove]);
10817
10818     return TRUE;
10819 }
10820
10821
10822 void
10823 CopyPlayerNameIntoFileName(dest, src)
10824      char **dest, *src;
10825 {
10826     while (*src != NULLCHAR && *src != ',') {
10827         if (*src == ' ') {
10828             *(*dest)++ = '_';
10829             src++;
10830         } else {
10831             *(*dest)++ = *src++;
10832         }
10833     }
10834 }
10835
10836 char *DefaultFileName(ext)
10837      char *ext;
10838 {
10839     static char def[MSG_SIZ];
10840     char *p;
10841
10842     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10843         p = def;
10844         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10845         *p++ = '-';
10846         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10847         *p++ = '.';
10848         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10849     } else {
10850         def[0] = NULLCHAR;
10851     }
10852     return def;
10853 }
10854
10855 /* Save the current game to the given file */
10856 int
10857 SaveGameToFile(filename, append)
10858      char *filename;
10859      int append;
10860 {
10861     FILE *f;
10862     char buf[MSG_SIZ];
10863
10864     if (strcmp(filename, "-") == 0) {
10865         return SaveGame(stdout, 0, NULL);
10866     } else {
10867         f = fopen(filename, append ? "a" : "w");
10868         if (f == NULL) {
10869             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10870             DisplayError(buf, errno);
10871             return FALSE;
10872         } else {
10873             return SaveGame(f, 0, NULL);
10874         }
10875     }
10876 }
10877
10878 char *
10879 SavePart(str)
10880      char *str;
10881 {
10882     static char buf[MSG_SIZ];
10883     char *p;
10884
10885     p = strchr(str, ' ');
10886     if (p == NULL) return str;
10887     strncpy(buf, str, p - str);
10888     buf[p - str] = NULLCHAR;
10889     return buf;
10890 }
10891
10892 #define PGN_MAX_LINE 75
10893
10894 #define PGN_SIDE_WHITE  0
10895 #define PGN_SIDE_BLACK  1
10896
10897 /* [AS] */
10898 static int FindFirstMoveOutOfBook( int side )
10899 {
10900     int result = -1;
10901
10902     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10903         int index = backwardMostMove;
10904         int has_book_hit = 0;
10905
10906         if( (index % 2) != side ) {
10907             index++;
10908         }
10909
10910         while( index < forwardMostMove ) {
10911             /* Check to see if engine is in book */
10912             int depth = pvInfoList[index].depth;
10913             int score = pvInfoList[index].score;
10914             int in_book = 0;
10915
10916             if( depth <= 2 ) {
10917                 in_book = 1;
10918             }
10919             else if( score == 0 && depth == 63 ) {
10920                 in_book = 1; /* Zappa */
10921             }
10922             else if( score == 2 && depth == 99 ) {
10923                 in_book = 1; /* Abrok */
10924             }
10925
10926             has_book_hit += in_book;
10927
10928             if( ! in_book ) {
10929                 result = index;
10930
10931                 break;
10932             }
10933
10934             index += 2;
10935         }
10936     }
10937
10938     return result;
10939 }
10940
10941 /* [AS] */
10942 void GetOutOfBookInfo( char * buf )
10943 {
10944     int oob[2];
10945     int i;
10946     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10947
10948     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10949     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10950
10951     *buf = '\0';
10952
10953     if( oob[0] >= 0 || oob[1] >= 0 ) {
10954         for( i=0; i<2; i++ ) {
10955             int idx = oob[i];
10956
10957             if( idx >= 0 ) {
10958                 if( i > 0 && oob[0] >= 0 ) {
10959                     strcat( buf, "   " );
10960                 }
10961
10962                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10963                 sprintf( buf+strlen(buf), "%s%.2f",
10964                     pvInfoList[idx].score >= 0 ? "+" : "",
10965                     pvInfoList[idx].score / 100.0 );
10966             }
10967         }
10968     }
10969 }
10970
10971 /* Save game in PGN style and close the file */
10972 int
10973 SaveGamePGN(f)
10974      FILE *f;
10975 {
10976     int i, offset, linelen, newblock;
10977     time_t tm;
10978 //    char *movetext;
10979     char numtext[32];
10980     int movelen, numlen, blank;
10981     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10982
10983     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10984
10985     tm = time((time_t *) NULL);
10986
10987     PrintPGNTags(f, &gameInfo);
10988
10989     if (backwardMostMove > 0 || startedFromSetupPosition) {
10990         char *fen = PositionToFEN(backwardMostMove, NULL);
10991         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10992         fprintf(f, "\n{--------------\n");
10993         PrintPosition(f, backwardMostMove);
10994         fprintf(f, "--------------}\n");
10995         free(fen);
10996     }
10997     else {
10998         /* [AS] Out of book annotation */
10999         if( appData.saveOutOfBookInfo ) {
11000             char buf[64];
11001
11002             GetOutOfBookInfo( buf );
11003
11004             if( buf[0] != '\0' ) {
11005                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11006             }
11007         }
11008
11009         fprintf(f, "\n");
11010     }
11011
11012     i = backwardMostMove;
11013     linelen = 0;
11014     newblock = TRUE;
11015
11016     while (i < forwardMostMove) {
11017         /* Print comments preceding this move */
11018         if (commentList[i] != NULL) {
11019             if (linelen > 0) fprintf(f, "\n");
11020             fprintf(f, "%s", commentList[i]);
11021             linelen = 0;
11022             newblock = TRUE;
11023         }
11024
11025         /* Format move number */
11026         if ((i % 2) == 0)
11027           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11028         else
11029           if (newblock)
11030             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11031           else
11032             numtext[0] = NULLCHAR;
11033
11034         numlen = strlen(numtext);
11035         newblock = FALSE;
11036
11037         /* Print move number */
11038         blank = linelen > 0 && numlen > 0;
11039         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11040             fprintf(f, "\n");
11041             linelen = 0;
11042             blank = 0;
11043         }
11044         if (blank) {
11045             fprintf(f, " ");
11046             linelen++;
11047         }
11048         fprintf(f, "%s", numtext);
11049         linelen += numlen;
11050
11051         /* Get move */
11052         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11053         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11054
11055         /* Print move */
11056         blank = linelen > 0 && movelen > 0;
11057         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11058             fprintf(f, "\n");
11059             linelen = 0;
11060             blank = 0;
11061         }
11062         if (blank) {
11063             fprintf(f, " ");
11064             linelen++;
11065         }
11066         fprintf(f, "%s", move_buffer);
11067         linelen += movelen;
11068
11069         /* [AS] Add PV info if present */
11070         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11071             /* [HGM] add time */
11072             char buf[MSG_SIZ]; int seconds;
11073
11074             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11075
11076             if( seconds <= 0)
11077               buf[0] = 0;
11078             else
11079               if( seconds < 30 )
11080                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11081               else
11082                 {
11083                   seconds = (seconds + 4)/10; // round to full seconds
11084                   if( seconds < 60 )
11085                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11086                   else
11087                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11088                 }
11089
11090             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11091                       pvInfoList[i].score >= 0 ? "+" : "",
11092                       pvInfoList[i].score / 100.0,
11093                       pvInfoList[i].depth,
11094                       buf );
11095
11096             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11097
11098             /* Print score/depth */
11099             blank = linelen > 0 && movelen > 0;
11100             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11101                 fprintf(f, "\n");
11102                 linelen = 0;
11103                 blank = 0;
11104             }
11105             if (blank) {
11106                 fprintf(f, " ");
11107                 linelen++;
11108             }
11109             fprintf(f, "%s", move_buffer);
11110             linelen += movelen;
11111         }
11112
11113         i++;
11114     }
11115
11116     /* Start a new line */
11117     if (linelen > 0) fprintf(f, "\n");
11118
11119     /* Print comments after last move */
11120     if (commentList[i] != NULL) {
11121         fprintf(f, "%s\n", commentList[i]);
11122     }
11123
11124     /* Print result */
11125     if (gameInfo.resultDetails != NULL &&
11126         gameInfo.resultDetails[0] != NULLCHAR) {
11127         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11128                 PGNResult(gameInfo.result));
11129     } else {
11130         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11131     }
11132
11133     fclose(f);
11134     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11135     return TRUE;
11136 }
11137
11138 /* Save game in old style and close the file */
11139 int
11140 SaveGameOldStyle(f)
11141      FILE *f;
11142 {
11143     int i, offset;
11144     time_t tm;
11145
11146     tm = time((time_t *) NULL);
11147
11148     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11149     PrintOpponents(f);
11150
11151     if (backwardMostMove > 0 || startedFromSetupPosition) {
11152         fprintf(f, "\n[--------------\n");
11153         PrintPosition(f, backwardMostMove);
11154         fprintf(f, "--------------]\n");
11155     } else {
11156         fprintf(f, "\n");
11157     }
11158
11159     i = backwardMostMove;
11160     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11161
11162     while (i < forwardMostMove) {
11163         if (commentList[i] != NULL) {
11164             fprintf(f, "[%s]\n", commentList[i]);
11165         }
11166
11167         if ((i % 2) == 1) {
11168             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11169             i++;
11170         } else {
11171             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11172             i++;
11173             if (commentList[i] != NULL) {
11174                 fprintf(f, "\n");
11175                 continue;
11176             }
11177             if (i >= forwardMostMove) {
11178                 fprintf(f, "\n");
11179                 break;
11180             }
11181             fprintf(f, "%s\n", parseList[i]);
11182             i++;
11183         }
11184     }
11185
11186     if (commentList[i] != NULL) {
11187         fprintf(f, "[%s]\n", commentList[i]);
11188     }
11189
11190     /* This isn't really the old style, but it's close enough */
11191     if (gameInfo.resultDetails != NULL &&
11192         gameInfo.resultDetails[0] != NULLCHAR) {
11193         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11194                 gameInfo.resultDetails);
11195     } else {
11196         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11197     }
11198
11199     fclose(f);
11200     return TRUE;
11201 }
11202
11203 /* Save the current game to open file f and close the file */
11204 int
11205 SaveGame(f, dummy, dummy2)
11206      FILE *f;
11207      int dummy;
11208      char *dummy2;
11209 {
11210     if (gameMode == EditPosition) EditPositionDone(TRUE);
11211     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11212     if (appData.oldSaveStyle)
11213       return SaveGameOldStyle(f);
11214     else
11215       return SaveGamePGN(f);
11216 }
11217
11218 /* Save the current position to the given file */
11219 int
11220 SavePositionToFile(filename)
11221      char *filename;
11222 {
11223     FILE *f;
11224     char buf[MSG_SIZ];
11225
11226     if (strcmp(filename, "-") == 0) {
11227         return SavePosition(stdout, 0, NULL);
11228     } else {
11229         f = fopen(filename, "a");
11230         if (f == NULL) {
11231             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11232             DisplayError(buf, errno);
11233             return FALSE;
11234         } else {
11235             SavePosition(f, 0, NULL);
11236             return TRUE;
11237         }
11238     }
11239 }
11240
11241 /* Save the current position to the given open file and close the file */
11242 int
11243 SavePosition(f, dummy, dummy2)
11244      FILE *f;
11245      int dummy;
11246      char *dummy2;
11247 {
11248     time_t tm;
11249     char *fen;
11250
11251     if (gameMode == EditPosition) EditPositionDone(TRUE);
11252     if (appData.oldSaveStyle) {
11253         tm = time((time_t *) NULL);
11254
11255         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11256         PrintOpponents(f);
11257         fprintf(f, "[--------------\n");
11258         PrintPosition(f, currentMove);
11259         fprintf(f, "--------------]\n");
11260     } else {
11261         fen = PositionToFEN(currentMove, NULL);
11262         fprintf(f, "%s\n", fen);
11263         free(fen);
11264     }
11265     fclose(f);
11266     return TRUE;
11267 }
11268
11269 void
11270 ReloadCmailMsgEvent(unregister)
11271      int unregister;
11272 {
11273 #if !WIN32
11274     static char *inFilename = NULL;
11275     static char *outFilename;
11276     int i;
11277     struct stat inbuf, outbuf;
11278     int status;
11279
11280     /* Any registered moves are unregistered if unregister is set, */
11281     /* i.e. invoked by the signal handler */
11282     if (unregister) {
11283         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11284             cmailMoveRegistered[i] = FALSE;
11285             if (cmailCommentList[i] != NULL) {
11286                 free(cmailCommentList[i]);
11287                 cmailCommentList[i] = NULL;
11288             }
11289         }
11290         nCmailMovesRegistered = 0;
11291     }
11292
11293     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11294         cmailResult[i] = CMAIL_NOT_RESULT;
11295     }
11296     nCmailResults = 0;
11297
11298     if (inFilename == NULL) {
11299         /* Because the filenames are static they only get malloced once  */
11300         /* and they never get freed                                      */
11301         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11302         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11303
11304         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11305         sprintf(outFilename, "%s.out", appData.cmailGameName);
11306     }
11307
11308     status = stat(outFilename, &outbuf);
11309     if (status < 0) {
11310         cmailMailedMove = FALSE;
11311     } else {
11312         status = stat(inFilename, &inbuf);
11313         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11314     }
11315
11316     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11317        counts the games, notes how each one terminated, etc.
11318
11319        It would be nice to remove this kludge and instead gather all
11320        the information while building the game list.  (And to keep it
11321        in the game list nodes instead of having a bunch of fixed-size
11322        parallel arrays.)  Note this will require getting each game's
11323        termination from the PGN tags, as the game list builder does
11324        not process the game moves.  --mann
11325        */
11326     cmailMsgLoaded = TRUE;
11327     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11328
11329     /* Load first game in the file or popup game menu */
11330     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11331
11332 #endif /* !WIN32 */
11333     return;
11334 }
11335
11336 int
11337 RegisterMove()
11338 {
11339     FILE *f;
11340     char string[MSG_SIZ];
11341
11342     if (   cmailMailedMove
11343         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11344         return TRUE;            /* Allow free viewing  */
11345     }
11346
11347     /* Unregister move to ensure that we don't leave RegisterMove        */
11348     /* with the move registered when the conditions for registering no   */
11349     /* longer hold                                                       */
11350     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11351         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11352         nCmailMovesRegistered --;
11353
11354         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11355           {
11356               free(cmailCommentList[lastLoadGameNumber - 1]);
11357               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11358           }
11359     }
11360
11361     if (cmailOldMove == -1) {
11362         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11363         return FALSE;
11364     }
11365
11366     if (currentMove > cmailOldMove + 1) {
11367         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11368         return FALSE;
11369     }
11370
11371     if (currentMove < cmailOldMove) {
11372         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11373         return FALSE;
11374     }
11375
11376     if (forwardMostMove > currentMove) {
11377         /* Silently truncate extra moves */
11378         TruncateGame();
11379     }
11380
11381     if (   (currentMove == cmailOldMove + 1)
11382         || (   (currentMove == cmailOldMove)
11383             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11384                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11385         if (gameInfo.result != GameUnfinished) {
11386             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11387         }
11388
11389         if (commentList[currentMove] != NULL) {
11390             cmailCommentList[lastLoadGameNumber - 1]
11391               = StrSave(commentList[currentMove]);
11392         }
11393         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11394
11395         if (appData.debugMode)
11396           fprintf(debugFP, "Saving %s for game %d\n",
11397                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11398
11399         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11400
11401         f = fopen(string, "w");
11402         if (appData.oldSaveStyle) {
11403             SaveGameOldStyle(f); /* also closes the file */
11404
11405             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11406             f = fopen(string, "w");
11407             SavePosition(f, 0, NULL); /* also closes the file */
11408         } else {
11409             fprintf(f, "{--------------\n");
11410             PrintPosition(f, currentMove);
11411             fprintf(f, "--------------}\n\n");
11412
11413             SaveGame(f, 0, NULL); /* also closes the file*/
11414         }
11415
11416         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11417         nCmailMovesRegistered ++;
11418     } else if (nCmailGames == 1) {
11419         DisplayError(_("You have not made a move yet"), 0);
11420         return FALSE;
11421     }
11422
11423     return TRUE;
11424 }
11425
11426 void
11427 MailMoveEvent()
11428 {
11429 #if !WIN32
11430     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11431     FILE *commandOutput;
11432     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11433     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11434     int nBuffers;
11435     int i;
11436     int archived;
11437     char *arcDir;
11438
11439     if (! cmailMsgLoaded) {
11440         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11441         return;
11442     }
11443
11444     if (nCmailGames == nCmailResults) {
11445         DisplayError(_("No unfinished games"), 0);
11446         return;
11447     }
11448
11449 #if CMAIL_PROHIBIT_REMAIL
11450     if (cmailMailedMove) {
11451       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);
11452         DisplayError(msg, 0);
11453         return;
11454     }
11455 #endif
11456
11457     if (! (cmailMailedMove || RegisterMove())) return;
11458
11459     if (   cmailMailedMove
11460         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11461       snprintf(string, MSG_SIZ, partCommandString,
11462                appData.debugMode ? " -v" : "", appData.cmailGameName);
11463         commandOutput = popen(string, "r");
11464
11465         if (commandOutput == NULL) {
11466             DisplayError(_("Failed to invoke cmail"), 0);
11467         } else {
11468             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11469                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11470             }
11471             if (nBuffers > 1) {
11472                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11473                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11474                 nBytes = MSG_SIZ - 1;
11475             } else {
11476                 (void) memcpy(msg, buffer, nBytes);
11477             }
11478             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11479
11480             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11481                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11482
11483                 archived = TRUE;
11484                 for (i = 0; i < nCmailGames; i ++) {
11485                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11486                         archived = FALSE;
11487                     }
11488                 }
11489                 if (   archived
11490                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11491                         != NULL)) {
11492                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11493                            arcDir,
11494                            appData.cmailGameName,
11495                            gameInfo.date);
11496                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11497                     cmailMsgLoaded = FALSE;
11498                 }
11499             }
11500
11501             DisplayInformation(msg);
11502             pclose(commandOutput);
11503         }
11504     } else {
11505         if ((*cmailMsg) != '\0') {
11506             DisplayInformation(cmailMsg);
11507         }
11508     }
11509
11510     return;
11511 #endif /* !WIN32 */
11512 }
11513
11514 char *
11515 CmailMsg()
11516 {
11517 #if WIN32
11518     return NULL;
11519 #else
11520     int  prependComma = 0;
11521     char number[5];
11522     char string[MSG_SIZ];       /* Space for game-list */
11523     int  i;
11524
11525     if (!cmailMsgLoaded) return "";
11526
11527     if (cmailMailedMove) {
11528       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11529     } else {
11530         /* Create a list of games left */
11531       snprintf(string, MSG_SIZ, "[");
11532         for (i = 0; i < nCmailGames; i ++) {
11533             if (! (   cmailMoveRegistered[i]
11534                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11535                 if (prependComma) {
11536                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11537                 } else {
11538                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11539                     prependComma = 1;
11540                 }
11541
11542                 strcat(string, number);
11543             }
11544         }
11545         strcat(string, "]");
11546
11547         if (nCmailMovesRegistered + nCmailResults == 0) {
11548             switch (nCmailGames) {
11549               case 1:
11550                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11551                 break;
11552
11553               case 2:
11554                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11555                 break;
11556
11557               default:
11558                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11559                          nCmailGames);
11560                 break;
11561             }
11562         } else {
11563             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11564               case 1:
11565                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11566                          string);
11567                 break;
11568
11569               case 0:
11570                 if (nCmailResults == nCmailGames) {
11571                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11572                 } else {
11573                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11574                 }
11575                 break;
11576
11577               default:
11578                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11579                          string);
11580             }
11581         }
11582     }
11583     return cmailMsg;
11584 #endif /* WIN32 */
11585 }
11586
11587 void
11588 ResetGameEvent()
11589 {
11590     if (gameMode == Training)
11591       SetTrainingModeOff();
11592
11593     Reset(TRUE, TRUE);
11594     cmailMsgLoaded = FALSE;
11595     if (appData.icsActive) {
11596       SendToICS(ics_prefix);
11597       SendToICS("refresh\n");
11598     }
11599 }
11600
11601 void
11602 ExitEvent(status)
11603      int status;
11604 {
11605     exiting++;
11606     if (exiting > 2) {
11607       /* Give up on clean exit */
11608       exit(status);
11609     }
11610     if (exiting > 1) {
11611       /* Keep trying for clean exit */
11612       return;
11613     }
11614
11615     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11616
11617     if (telnetISR != NULL) {
11618       RemoveInputSource(telnetISR);
11619     }
11620     if (icsPR != NoProc) {
11621       DestroyChildProcess(icsPR, TRUE);
11622     }
11623
11624     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11625     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11626
11627     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11628     /* make sure this other one finishes before killing it!                  */
11629     if(endingGame) { int count = 0;
11630         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11631         while(endingGame && count++ < 10) DoSleep(1);
11632         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11633     }
11634
11635     /* Kill off chess programs */
11636     if (first.pr != NoProc) {
11637         ExitAnalyzeMode();
11638
11639         DoSleep( appData.delayBeforeQuit );
11640         SendToProgram("quit\n", &first);
11641         DoSleep( appData.delayAfterQuit );
11642         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11643     }
11644     if (second.pr != NoProc) {
11645         DoSleep( appData.delayBeforeQuit );
11646         SendToProgram("quit\n", &second);
11647         DoSleep( appData.delayAfterQuit );
11648         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11649     }
11650     if (first.isr != NULL) {
11651         RemoveInputSource(first.isr);
11652     }
11653     if (second.isr != NULL) {
11654         RemoveInputSource(second.isr);
11655     }
11656
11657     ShutDownFrontEnd();
11658     exit(status);
11659 }
11660
11661 void
11662 PauseEvent()
11663 {
11664     if (appData.debugMode)
11665         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11666     if (pausing) {
11667         pausing = FALSE;
11668         ModeHighlight();
11669         if (gameMode == MachinePlaysWhite ||
11670             gameMode == MachinePlaysBlack) {
11671             StartClocks();
11672         } else {
11673             DisplayBothClocks();
11674         }
11675         if (gameMode == PlayFromGameFile) {
11676             if (appData.timeDelay >= 0)
11677                 AutoPlayGameLoop();
11678         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11679             Reset(FALSE, TRUE);
11680             SendToICS(ics_prefix);
11681             SendToICS("refresh\n");
11682         } else if (currentMove < forwardMostMove) {
11683             ForwardInner(forwardMostMove);
11684         }
11685         pauseExamInvalid = FALSE;
11686     } else {
11687         switch (gameMode) {
11688           default:
11689             return;
11690           case IcsExamining:
11691             pauseExamForwardMostMove = forwardMostMove;
11692             pauseExamInvalid = FALSE;
11693             /* fall through */
11694           case IcsObserving:
11695           case IcsPlayingWhite:
11696           case IcsPlayingBlack:
11697             pausing = TRUE;
11698             ModeHighlight();
11699             return;
11700           case PlayFromGameFile:
11701             (void) StopLoadGameTimer();
11702             pausing = TRUE;
11703             ModeHighlight();
11704             break;
11705           case BeginningOfGame:
11706             if (appData.icsActive) return;
11707             /* else fall through */
11708           case MachinePlaysWhite:
11709           case MachinePlaysBlack:
11710           case TwoMachinesPlay:
11711             if (forwardMostMove == 0)
11712               return;           /* don't pause if no one has moved */
11713             if ((gameMode == MachinePlaysWhite &&
11714                  !WhiteOnMove(forwardMostMove)) ||
11715                 (gameMode == MachinePlaysBlack &&
11716                  WhiteOnMove(forwardMostMove))) {
11717                 StopClocks();
11718             }
11719             pausing = TRUE;
11720             ModeHighlight();
11721             break;
11722         }
11723     }
11724 }
11725
11726 void
11727 EditCommentEvent()
11728 {
11729     char title[MSG_SIZ];
11730
11731     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11732       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11733     } else {
11734       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11735                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11736                parseList[currentMove - 1]);
11737     }
11738
11739     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11740 }
11741
11742
11743 void
11744 EditTagsEvent()
11745 {
11746     char *tags = PGNTags(&gameInfo);
11747     EditTagsPopUp(tags, NULL);
11748     free(tags);
11749 }
11750
11751 void
11752 AnalyzeModeEvent()
11753 {
11754     if (appData.noChessProgram || gameMode == AnalyzeMode)
11755       return;
11756
11757     if (gameMode != AnalyzeFile) {
11758         if (!appData.icsEngineAnalyze) {
11759                EditGameEvent();
11760                if (gameMode != EditGame) return;
11761         }
11762         ResurrectChessProgram();
11763         SendToProgram("analyze\n", &first);
11764         first.analyzing = TRUE;
11765         /*first.maybeThinking = TRUE;*/
11766         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11767         EngineOutputPopUp();
11768     }
11769     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11770     pausing = FALSE;
11771     ModeHighlight();
11772     SetGameInfo();
11773
11774     StartAnalysisClock();
11775     GetTimeMark(&lastNodeCountTime);
11776     lastNodeCount = 0;
11777 }
11778
11779 void
11780 AnalyzeFileEvent()
11781 {
11782     if (appData.noChessProgram || gameMode == AnalyzeFile)
11783       return;
11784
11785     if (gameMode != AnalyzeMode) {
11786         EditGameEvent();
11787         if (gameMode != EditGame) return;
11788         ResurrectChessProgram();
11789         SendToProgram("analyze\n", &first);
11790         first.analyzing = TRUE;
11791         /*first.maybeThinking = TRUE;*/
11792         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11793         EngineOutputPopUp();
11794     }
11795     gameMode = AnalyzeFile;
11796     pausing = FALSE;
11797     ModeHighlight();
11798     SetGameInfo();
11799
11800     StartAnalysisClock();
11801     GetTimeMark(&lastNodeCountTime);
11802     lastNodeCount = 0;
11803 }
11804
11805 void
11806 MachineWhiteEvent()
11807 {
11808     char buf[MSG_SIZ];
11809     char *bookHit = NULL;
11810
11811     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11812       return;
11813
11814
11815     if (gameMode == PlayFromGameFile ||
11816         gameMode == TwoMachinesPlay  ||
11817         gameMode == Training         ||
11818         gameMode == AnalyzeMode      ||
11819         gameMode == EndOfGame)
11820         EditGameEvent();
11821
11822     if (gameMode == EditPosition)
11823         EditPositionDone(TRUE);
11824
11825     if (!WhiteOnMove(currentMove)) {
11826         DisplayError(_("It is not White's turn"), 0);
11827         return;
11828     }
11829
11830     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11831       ExitAnalyzeMode();
11832
11833     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11834         gameMode == AnalyzeFile)
11835         TruncateGame();
11836
11837     ResurrectChessProgram();    /* in case it isn't running */
11838     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11839         gameMode = MachinePlaysWhite;
11840         ResetClocks();
11841     } else
11842     gameMode = MachinePlaysWhite;
11843     pausing = FALSE;
11844     ModeHighlight();
11845     SetGameInfo();
11846     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11847     DisplayTitle(buf);
11848     if (first.sendName) {
11849       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11850       SendToProgram(buf, &first);
11851     }
11852     if (first.sendTime) {
11853       if (first.useColors) {
11854         SendToProgram("black\n", &first); /*gnu kludge*/
11855       }
11856       SendTimeRemaining(&first, TRUE);
11857     }
11858     if (first.useColors) {
11859       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11860     }
11861     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11862     SetMachineThinkingEnables();
11863     first.maybeThinking = TRUE;
11864     StartClocks();
11865     firstMove = FALSE;
11866
11867     if (appData.autoFlipView && !flipView) {
11868       flipView = !flipView;
11869       DrawPosition(FALSE, NULL);
11870       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11871     }
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 void
11887 MachineBlackEvent()
11888 {
11889   char buf[MSG_SIZ];
11890   char *bookHit = NULL;
11891
11892     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11893         return;
11894
11895
11896     if (gameMode == PlayFromGameFile ||
11897         gameMode == TwoMachinesPlay  ||
11898         gameMode == Training         ||
11899         gameMode == AnalyzeMode      ||
11900         gameMode == EndOfGame)
11901         EditGameEvent();
11902
11903     if (gameMode == EditPosition)
11904         EditPositionDone(TRUE);
11905
11906     if (WhiteOnMove(currentMove)) {
11907         DisplayError(_("It is not Black's turn"), 0);
11908         return;
11909     }
11910
11911     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11912       ExitAnalyzeMode();
11913
11914     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11915         gameMode == AnalyzeFile)
11916         TruncateGame();
11917
11918     ResurrectChessProgram();    /* in case it isn't running */
11919     gameMode = MachinePlaysBlack;
11920     pausing = FALSE;
11921     ModeHighlight();
11922     SetGameInfo();
11923     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11924     DisplayTitle(buf);
11925     if (first.sendName) {
11926       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11927       SendToProgram(buf, &first);
11928     }
11929     if (first.sendTime) {
11930       if (first.useColors) {
11931         SendToProgram("white\n", &first); /*gnu kludge*/
11932       }
11933       SendTimeRemaining(&first, FALSE);
11934     }
11935     if (first.useColors) {
11936       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11937     }
11938     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11939     SetMachineThinkingEnables();
11940     first.maybeThinking = TRUE;
11941     StartClocks();
11942
11943     if (appData.autoFlipView && flipView) {
11944       flipView = !flipView;
11945       DrawPosition(FALSE, NULL);
11946       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11947     }
11948     if(bookHit) { // [HGM] book: simulate book reply
11949         static char bookMove[MSG_SIZ]; // a bit generous?
11950
11951         programStats.nodes = programStats.depth = programStats.time =
11952         programStats.score = programStats.got_only_move = 0;
11953         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11954
11955         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11956         strcat(bookMove, bookHit);
11957         HandleMachineMove(bookMove, &first);
11958     }
11959 }
11960
11961
11962 void
11963 DisplayTwoMachinesTitle()
11964 {
11965     char buf[MSG_SIZ];
11966     if (appData.matchGames > 0) {
11967         if (first.twoMachinesColor[0] == 'w') {
11968           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11969                    gameInfo.white, gameInfo.black,
11970                    first.matchWins, second.matchWins,
11971                    matchGame - 1 - (first.matchWins + second.matchWins));
11972         } else {
11973           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11974                    gameInfo.white, gameInfo.black,
11975                    second.matchWins, first.matchWins,
11976                    matchGame - 1 - (first.matchWins + second.matchWins));
11977         }
11978     } else {
11979       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11980     }
11981     DisplayTitle(buf);
11982 }
11983
11984 void
11985 SettingsMenuIfReady()
11986 {
11987   if (second.lastPing != second.lastPong) {
11988     DisplayMessage("", _("Waiting for second chess program"));
11989     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11990     return;
11991   }
11992   ThawUI();
11993   DisplayMessage("", "");
11994   SettingsPopUp(&second);
11995 }
11996
11997 int
11998 WaitForSecond(DelayedEventCallback retry)
11999 {
12000     if (second.pr == NULL) {
12001         StartChessProgram(&second);
12002         if (second.protocolVersion == 1) {
12003           retry();
12004         } else {
12005           /* kludge: allow timeout for initial "feature" command */
12006           FreezeUI();
12007           DisplayMessage("", _("Starting second chess program"));
12008           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12009         }
12010         return 1;
12011     }
12012     return 0;
12013 }
12014
12015 void
12016 TwoMachinesEvent P((void))
12017 {
12018     int i;
12019     char buf[MSG_SIZ];
12020     ChessProgramState *onmove;
12021     char *bookHit = NULL;
12022
12023     if (appData.noChessProgram) return;
12024
12025     switch (gameMode) {
12026       case TwoMachinesPlay:
12027         return;
12028       case MachinePlaysWhite:
12029       case MachinePlaysBlack:
12030         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12031             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12032             return;
12033         }
12034         /* fall through */
12035       case BeginningOfGame:
12036       case PlayFromGameFile:
12037       case EndOfGame:
12038         EditGameEvent();
12039         if (gameMode != EditGame) return;
12040         break;
12041       case EditPosition:
12042         EditPositionDone(TRUE);
12043         break;
12044       case AnalyzeMode:
12045       case AnalyzeFile:
12046         ExitAnalyzeMode();
12047         break;
12048       case EditGame:
12049       default:
12050         break;
12051     }
12052
12053 //    forwardMostMove = currentMove;
12054     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12055     ResurrectChessProgram();    /* in case first program isn't running */
12056
12057     if(WaitForSecond(TwoMachinesEventIfReady)) return;
12058     DisplayMessage("", "");
12059     InitChessProgram(&second, FALSE);
12060     SendToProgram("force\n", &second);
12061     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12062       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12063       return;
12064     }
12065     if (startedFromSetupPosition) {
12066         SendBoard(&second, backwardMostMove);
12067     if (appData.debugMode) {
12068         fprintf(debugFP, "Two Machines\n");
12069     }
12070     }
12071     for (i = backwardMostMove; i < forwardMostMove; i++) {
12072         SendMoveToProgram(i, &second);
12073     }
12074
12075     gameMode = TwoMachinesPlay;
12076     pausing = FALSE;
12077     ModeHighlight();
12078     SetGameInfo();
12079     DisplayTwoMachinesTitle();
12080     firstMove = TRUE;
12081     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12082         onmove = &first;
12083     } else {
12084         onmove = &second;
12085     }
12086
12087     SendToProgram(first.computerString, &first);
12088     if (first.sendName) {
12089       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12090       SendToProgram(buf, &first);
12091     }
12092     SendToProgram(second.computerString, &second);
12093     if (second.sendName) {
12094       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12095       SendToProgram(buf, &second);
12096     }
12097
12098     ResetClocks();
12099     if (!first.sendTime || !second.sendTime) {
12100         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12101         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12102     }
12103     if (onmove->sendTime) {
12104       if (onmove->useColors) {
12105         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12106       }
12107       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12108     }
12109     if (onmove->useColors) {
12110       SendToProgram(onmove->twoMachinesColor, onmove);
12111     }
12112     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12113 //    SendToProgram("go\n", onmove);
12114     onmove->maybeThinking = TRUE;
12115     SetMachineThinkingEnables();
12116
12117     StartClocks();
12118
12119     if(bookHit) { // [HGM] book: simulate book reply
12120         static char bookMove[MSG_SIZ]; // a bit generous?
12121
12122         programStats.nodes = programStats.depth = programStats.time =
12123         programStats.score = programStats.got_only_move = 0;
12124         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12125
12126         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12127         strcat(bookMove, bookHit);
12128         savedMessage = bookMove; // args for deferred call
12129         savedState = onmove;
12130         ScheduleDelayedEvent(DeferredBookMove, 1);
12131     }
12132 }
12133
12134 void
12135 TrainingEvent()
12136 {
12137     if (gameMode == Training) {
12138       SetTrainingModeOff();
12139       gameMode = PlayFromGameFile;
12140       DisplayMessage("", _("Training mode off"));
12141     } else {
12142       gameMode = Training;
12143       animateTraining = appData.animate;
12144
12145       /* make sure we are not already at the end of the game */
12146       if (currentMove < forwardMostMove) {
12147         SetTrainingModeOn();
12148         DisplayMessage("", _("Training mode on"));
12149       } else {
12150         gameMode = PlayFromGameFile;
12151         DisplayError(_("Already at end of game"), 0);
12152       }
12153     }
12154     ModeHighlight();
12155 }
12156
12157 void
12158 IcsClientEvent()
12159 {
12160     if (!appData.icsActive) return;
12161     switch (gameMode) {
12162       case IcsPlayingWhite:
12163       case IcsPlayingBlack:
12164       case IcsObserving:
12165       case IcsIdle:
12166       case BeginningOfGame:
12167       case IcsExamining:
12168         return;
12169
12170       case EditGame:
12171         break;
12172
12173       case EditPosition:
12174         EditPositionDone(TRUE);
12175         break;
12176
12177       case AnalyzeMode:
12178       case AnalyzeFile:
12179         ExitAnalyzeMode();
12180         break;
12181
12182       default:
12183         EditGameEvent();
12184         break;
12185     }
12186
12187     gameMode = IcsIdle;
12188     ModeHighlight();
12189     return;
12190 }
12191
12192
12193 void
12194 EditGameEvent()
12195 {
12196     int i;
12197
12198     switch (gameMode) {
12199       case Training:
12200         SetTrainingModeOff();
12201         break;
12202       case MachinePlaysWhite:
12203       case MachinePlaysBlack:
12204       case BeginningOfGame:
12205         SendToProgram("force\n", &first);
12206         SetUserThinkingEnables();
12207         break;
12208       case PlayFromGameFile:
12209         (void) StopLoadGameTimer();
12210         if (gameFileFP != NULL) {
12211             gameFileFP = NULL;
12212         }
12213         break;
12214       case EditPosition:
12215         EditPositionDone(TRUE);
12216         break;
12217       case AnalyzeMode:
12218       case AnalyzeFile:
12219         ExitAnalyzeMode();
12220         SendToProgram("force\n", &first);
12221         break;
12222       case TwoMachinesPlay:
12223         GameEnds(EndOfFile, NULL, GE_PLAYER);
12224         ResurrectChessProgram();
12225         SetUserThinkingEnables();
12226         break;
12227       case EndOfGame:
12228         ResurrectChessProgram();
12229         break;
12230       case IcsPlayingBlack:
12231       case IcsPlayingWhite:
12232         DisplayError(_("Warning: You are still playing a game"), 0);
12233         break;
12234       case IcsObserving:
12235         DisplayError(_("Warning: You are still observing a game"), 0);
12236         break;
12237       case IcsExamining:
12238         DisplayError(_("Warning: You are still examining a game"), 0);
12239         break;
12240       case IcsIdle:
12241         break;
12242       case EditGame:
12243       default:
12244         return;
12245     }
12246
12247     pausing = FALSE;
12248     StopClocks();
12249     first.offeredDraw = second.offeredDraw = 0;
12250
12251     if (gameMode == PlayFromGameFile) {
12252         whiteTimeRemaining = timeRemaining[0][currentMove];
12253         blackTimeRemaining = timeRemaining[1][currentMove];
12254         DisplayTitle("");
12255     }
12256
12257     if (gameMode == MachinePlaysWhite ||
12258         gameMode == MachinePlaysBlack ||
12259         gameMode == TwoMachinesPlay ||
12260         gameMode == EndOfGame) {
12261         i = forwardMostMove;
12262         while (i > currentMove) {
12263             SendToProgram("undo\n", &first);
12264             i--;
12265         }
12266         whiteTimeRemaining = timeRemaining[0][currentMove];
12267         blackTimeRemaining = timeRemaining[1][currentMove];
12268         DisplayBothClocks();
12269         if (whiteFlag || blackFlag) {
12270             whiteFlag = blackFlag = 0;
12271         }
12272         DisplayTitle("");
12273     }
12274
12275     gameMode = EditGame;
12276     ModeHighlight();
12277     SetGameInfo();
12278 }
12279
12280
12281 void
12282 EditPositionEvent()
12283 {
12284     if (gameMode == EditPosition) {
12285         EditGameEvent();
12286         return;
12287     }
12288
12289     EditGameEvent();
12290     if (gameMode != EditGame) return;
12291
12292     gameMode = EditPosition;
12293     ModeHighlight();
12294     SetGameInfo();
12295     if (currentMove > 0)
12296       CopyBoard(boards[0], boards[currentMove]);
12297
12298     blackPlaysFirst = !WhiteOnMove(currentMove);
12299     ResetClocks();
12300     currentMove = forwardMostMove = backwardMostMove = 0;
12301     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12302     DisplayMove(-1);
12303 }
12304
12305 void
12306 ExitAnalyzeMode()
12307 {
12308     /* [DM] icsEngineAnalyze - possible call from other functions */
12309     if (appData.icsEngineAnalyze) {
12310         appData.icsEngineAnalyze = FALSE;
12311
12312         DisplayMessage("",_("Close ICS engine analyze..."));
12313     }
12314     if (first.analysisSupport && first.analyzing) {
12315       SendToProgram("exit\n", &first);
12316       first.analyzing = FALSE;
12317     }
12318     thinkOutput[0] = NULLCHAR;
12319 }
12320
12321 void
12322 EditPositionDone(Boolean fakeRights)
12323 {
12324     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12325
12326     startedFromSetupPosition = TRUE;
12327     InitChessProgram(&first, FALSE);
12328     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12329       boards[0][EP_STATUS] = EP_NONE;
12330       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12331     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12332         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12333         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12334       } else boards[0][CASTLING][2] = NoRights;
12335     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12336         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12337         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12338       } else boards[0][CASTLING][5] = NoRights;
12339     }
12340     SendToProgram("force\n", &first);
12341     if (blackPlaysFirst) {
12342         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12343         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12344         currentMove = forwardMostMove = backwardMostMove = 1;
12345         CopyBoard(boards[1], boards[0]);
12346     } else {
12347         currentMove = forwardMostMove = backwardMostMove = 0;
12348     }
12349     SendBoard(&first, forwardMostMove);
12350     if (appData.debugMode) {
12351         fprintf(debugFP, "EditPosDone\n");
12352     }
12353     DisplayTitle("");
12354     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12355     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12356     gameMode = EditGame;
12357     ModeHighlight();
12358     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12359     ClearHighlights(); /* [AS] */
12360 }
12361
12362 /* Pause for `ms' milliseconds */
12363 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12364 void
12365 TimeDelay(ms)
12366      long ms;
12367 {
12368     TimeMark m1, m2;
12369
12370     GetTimeMark(&m1);
12371     do {
12372         GetTimeMark(&m2);
12373     } while (SubtractTimeMarks(&m2, &m1) < ms);
12374 }
12375
12376 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12377 void
12378 SendMultiLineToICS(buf)
12379      char *buf;
12380 {
12381     char temp[MSG_SIZ+1], *p;
12382     int len;
12383
12384     len = strlen(buf);
12385     if (len > MSG_SIZ)
12386       len = MSG_SIZ;
12387
12388     strncpy(temp, buf, len);
12389     temp[len] = 0;
12390
12391     p = temp;
12392     while (*p) {
12393         if (*p == '\n' || *p == '\r')
12394           *p = ' ';
12395         ++p;
12396     }
12397
12398     strcat(temp, "\n");
12399     SendToICS(temp);
12400     SendToPlayer(temp, strlen(temp));
12401 }
12402
12403 void
12404 SetWhiteToPlayEvent()
12405 {
12406     if (gameMode == EditPosition) {
12407         blackPlaysFirst = FALSE;
12408         DisplayBothClocks();    /* works because currentMove is 0 */
12409     } else if (gameMode == IcsExamining) {
12410         SendToICS(ics_prefix);
12411         SendToICS("tomove white\n");
12412     }
12413 }
12414
12415 void
12416 SetBlackToPlayEvent()
12417 {
12418     if (gameMode == EditPosition) {
12419         blackPlaysFirst = TRUE;
12420         currentMove = 1;        /* kludge */
12421         DisplayBothClocks();
12422         currentMove = 0;
12423     } else if (gameMode == IcsExamining) {
12424         SendToICS(ics_prefix);
12425         SendToICS("tomove black\n");
12426     }
12427 }
12428
12429 void
12430 EditPositionMenuEvent(selection, x, y)
12431      ChessSquare selection;
12432      int x, y;
12433 {
12434     char buf[MSG_SIZ];
12435     ChessSquare piece = boards[0][y][x];
12436
12437     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12438
12439     switch (selection) {
12440       case ClearBoard:
12441         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12442             SendToICS(ics_prefix);
12443             SendToICS("bsetup clear\n");
12444         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12445             SendToICS(ics_prefix);
12446             SendToICS("clearboard\n");
12447         } else {
12448             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12449                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12450                 for (y = 0; y < BOARD_HEIGHT; y++) {
12451                     if (gameMode == IcsExamining) {
12452                         if (boards[currentMove][y][x] != EmptySquare) {
12453                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12454                                     AAA + x, ONE + y);
12455                             SendToICS(buf);
12456                         }
12457                     } else {
12458                         boards[0][y][x] = p;
12459                     }
12460                 }
12461             }
12462         }
12463         if (gameMode == EditPosition) {
12464             DrawPosition(FALSE, boards[0]);
12465         }
12466         break;
12467
12468       case WhitePlay:
12469         SetWhiteToPlayEvent();
12470         break;
12471
12472       case BlackPlay:
12473         SetBlackToPlayEvent();
12474         break;
12475
12476       case EmptySquare:
12477         if (gameMode == IcsExamining) {
12478             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12479             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12480             SendToICS(buf);
12481         } else {
12482             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12483                 if(x == BOARD_LEFT-2) {
12484                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12485                     boards[0][y][1] = 0;
12486                 } else
12487                 if(x == BOARD_RGHT+1) {
12488                     if(y >= gameInfo.holdingsSize) break;
12489                     boards[0][y][BOARD_WIDTH-2] = 0;
12490                 } else break;
12491             }
12492             boards[0][y][x] = EmptySquare;
12493             DrawPosition(FALSE, boards[0]);
12494         }
12495         break;
12496
12497       case PromotePiece:
12498         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12499            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12500             selection = (ChessSquare) (PROMOTED piece);
12501         } else if(piece == EmptySquare) selection = WhiteSilver;
12502         else selection = (ChessSquare)((int)piece - 1);
12503         goto defaultlabel;
12504
12505       case DemotePiece:
12506         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12507            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12508             selection = (ChessSquare) (DEMOTED piece);
12509         } else if(piece == EmptySquare) selection = BlackSilver;
12510         else selection = (ChessSquare)((int)piece + 1);
12511         goto defaultlabel;
12512
12513       case WhiteQueen:
12514       case BlackQueen:
12515         if(gameInfo.variant == VariantShatranj ||
12516            gameInfo.variant == VariantXiangqi  ||
12517            gameInfo.variant == VariantCourier  ||
12518            gameInfo.variant == VariantMakruk     )
12519             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12520         goto defaultlabel;
12521
12522       case WhiteKing:
12523       case BlackKing:
12524         if(gameInfo.variant == VariantXiangqi)
12525             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12526         if(gameInfo.variant == VariantKnightmate)
12527             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12528       default:
12529         defaultlabel:
12530         if (gameMode == IcsExamining) {
12531             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12532             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12533                      PieceToChar(selection), AAA + x, ONE + y);
12534             SendToICS(buf);
12535         } else {
12536             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12537                 int n;
12538                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12539                     n = PieceToNumber(selection - BlackPawn);
12540                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12541                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12542                     boards[0][BOARD_HEIGHT-1-n][1]++;
12543                 } else
12544                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12545                     n = PieceToNumber(selection);
12546                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12547                     boards[0][n][BOARD_WIDTH-1] = selection;
12548                     boards[0][n][BOARD_WIDTH-2]++;
12549                 }
12550             } else
12551             boards[0][y][x] = selection;
12552             DrawPosition(TRUE, boards[0]);
12553         }
12554         break;
12555     }
12556 }
12557
12558
12559 void
12560 DropMenuEvent(selection, x, y)
12561      ChessSquare selection;
12562      int x, y;
12563 {
12564     ChessMove moveType;
12565
12566     switch (gameMode) {
12567       case IcsPlayingWhite:
12568       case MachinePlaysBlack:
12569         if (!WhiteOnMove(currentMove)) {
12570             DisplayMoveError(_("It is Black's turn"));
12571             return;
12572         }
12573         moveType = WhiteDrop;
12574         break;
12575       case IcsPlayingBlack:
12576       case MachinePlaysWhite:
12577         if (WhiteOnMove(currentMove)) {
12578             DisplayMoveError(_("It is White's turn"));
12579             return;
12580         }
12581         moveType = BlackDrop;
12582         break;
12583       case EditGame:
12584         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12585         break;
12586       default:
12587         return;
12588     }
12589
12590     if (moveType == BlackDrop && selection < BlackPawn) {
12591       selection = (ChessSquare) ((int) selection
12592                                  + (int) BlackPawn - (int) WhitePawn);
12593     }
12594     if (boards[currentMove][y][x] != EmptySquare) {
12595         DisplayMoveError(_("That square is occupied"));
12596         return;
12597     }
12598
12599     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12600 }
12601
12602 void
12603 AcceptEvent()
12604 {
12605     /* Accept a pending offer of any kind from opponent */
12606
12607     if (appData.icsActive) {
12608         SendToICS(ics_prefix);
12609         SendToICS("accept\n");
12610     } else if (cmailMsgLoaded) {
12611         if (currentMove == cmailOldMove &&
12612             commentList[cmailOldMove] != NULL &&
12613             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12614                    "Black offers a draw" : "White offers a draw")) {
12615             TruncateGame();
12616             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12617             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12618         } else {
12619             DisplayError(_("There is no pending offer on this move"), 0);
12620             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12621         }
12622     } else {
12623         /* Not used for offers from chess program */
12624     }
12625 }
12626
12627 void
12628 DeclineEvent()
12629 {
12630     /* Decline a pending offer of any kind from opponent */
12631
12632     if (appData.icsActive) {
12633         SendToICS(ics_prefix);
12634         SendToICS("decline\n");
12635     } else if (cmailMsgLoaded) {
12636         if (currentMove == cmailOldMove &&
12637             commentList[cmailOldMove] != NULL &&
12638             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12639                    "Black offers a draw" : "White offers a draw")) {
12640 #ifdef NOTDEF
12641             AppendComment(cmailOldMove, "Draw declined", TRUE);
12642             DisplayComment(cmailOldMove - 1, "Draw declined");
12643 #endif /*NOTDEF*/
12644         } else {
12645             DisplayError(_("There is no pending offer on this move"), 0);
12646         }
12647     } else {
12648         /* Not used for offers from chess program */
12649     }
12650 }
12651
12652 void
12653 RematchEvent()
12654 {
12655     /* Issue ICS rematch command */
12656     if (appData.icsActive) {
12657         SendToICS(ics_prefix);
12658         SendToICS("rematch\n");
12659     }
12660 }
12661
12662 void
12663 CallFlagEvent()
12664 {
12665     /* Call your opponent's flag (claim a win on time) */
12666     if (appData.icsActive) {
12667         SendToICS(ics_prefix);
12668         SendToICS("flag\n");
12669     } else {
12670         switch (gameMode) {
12671           default:
12672             return;
12673           case MachinePlaysWhite:
12674             if (whiteFlag) {
12675                 if (blackFlag)
12676                   GameEnds(GameIsDrawn, "Both players ran out of time",
12677                            GE_PLAYER);
12678                 else
12679                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12680             } else {
12681                 DisplayError(_("Your opponent is not out of time"), 0);
12682             }
12683             break;
12684           case MachinePlaysBlack:
12685             if (blackFlag) {
12686                 if (whiteFlag)
12687                   GameEnds(GameIsDrawn, "Both players ran out of time",
12688                            GE_PLAYER);
12689                 else
12690                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12691             } else {
12692                 DisplayError(_("Your opponent is not out of time"), 0);
12693             }
12694             break;
12695         }
12696     }
12697 }
12698
12699 void
12700 ClockClick(int which)
12701 {       // [HGM] code moved to back-end from winboard.c
12702         if(which) { // black clock
12703           if (gameMode == EditPosition || gameMode == IcsExamining) {
12704             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12705             SetBlackToPlayEvent();
12706           } else if (gameMode == EditGame || shiftKey) {
12707             AdjustClock(which, -1);
12708           } else if (gameMode == IcsPlayingWhite ||
12709                      gameMode == MachinePlaysBlack) {
12710             CallFlagEvent();
12711           }
12712         } else { // white clock
12713           if (gameMode == EditPosition || gameMode == IcsExamining) {
12714             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12715             SetWhiteToPlayEvent();
12716           } else if (gameMode == EditGame || shiftKey) {
12717             AdjustClock(which, -1);
12718           } else if (gameMode == IcsPlayingBlack ||
12719                    gameMode == MachinePlaysWhite) {
12720             CallFlagEvent();
12721           }
12722         }
12723 }
12724
12725 void
12726 DrawEvent()
12727 {
12728     /* Offer draw or accept pending draw offer from opponent */
12729
12730     if (appData.icsActive) {
12731         /* Note: tournament rules require draw offers to be
12732            made after you make your move but before you punch
12733            your clock.  Currently ICS doesn't let you do that;
12734            instead, you immediately punch your clock after making
12735            a move, but you can offer a draw at any time. */
12736
12737         SendToICS(ics_prefix);
12738         SendToICS("draw\n");
12739         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12740     } else if (cmailMsgLoaded) {
12741         if (currentMove == cmailOldMove &&
12742             commentList[cmailOldMove] != NULL &&
12743             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12744                    "Black offers a draw" : "White offers a draw")) {
12745             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12746             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12747         } else if (currentMove == cmailOldMove + 1) {
12748             char *offer = WhiteOnMove(cmailOldMove) ?
12749               "White offers a draw" : "Black offers a draw";
12750             AppendComment(currentMove, offer, TRUE);
12751             DisplayComment(currentMove - 1, offer);
12752             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12753         } else {
12754             DisplayError(_("You must make your move before offering a draw"), 0);
12755             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12756         }
12757     } else if (first.offeredDraw) {
12758         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12759     } else {
12760         if (first.sendDrawOffers) {
12761             SendToProgram("draw\n", &first);
12762             userOfferedDraw = TRUE;
12763         }
12764     }
12765 }
12766
12767 void
12768 AdjournEvent()
12769 {
12770     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12771
12772     if (appData.icsActive) {
12773         SendToICS(ics_prefix);
12774         SendToICS("adjourn\n");
12775     } else {
12776         /* Currently GNU Chess doesn't offer or accept Adjourns */
12777     }
12778 }
12779
12780
12781 void
12782 AbortEvent()
12783 {
12784     /* Offer Abort or accept pending Abort offer from opponent */
12785
12786     if (appData.icsActive) {
12787         SendToICS(ics_prefix);
12788         SendToICS("abort\n");
12789     } else {
12790         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12791     }
12792 }
12793
12794 void
12795 ResignEvent()
12796 {
12797     /* Resign.  You can do this even if it's not your turn. */
12798
12799     if (appData.icsActive) {
12800         SendToICS(ics_prefix);
12801         SendToICS("resign\n");
12802     } else {
12803         switch (gameMode) {
12804           case MachinePlaysWhite:
12805             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12806             break;
12807           case MachinePlaysBlack:
12808             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12809             break;
12810           case EditGame:
12811             if (cmailMsgLoaded) {
12812                 TruncateGame();
12813                 if (WhiteOnMove(cmailOldMove)) {
12814                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12815                 } else {
12816                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12817                 }
12818                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12819             }
12820             break;
12821           default:
12822             break;
12823         }
12824     }
12825 }
12826
12827
12828 void
12829 StopObservingEvent()
12830 {
12831     /* Stop observing current games */
12832     SendToICS(ics_prefix);
12833     SendToICS("unobserve\n");
12834 }
12835
12836 void
12837 StopExaminingEvent()
12838 {
12839     /* Stop observing current game */
12840     SendToICS(ics_prefix);
12841     SendToICS("unexamine\n");
12842 }
12843
12844 void
12845 ForwardInner(target)
12846      int target;
12847 {
12848     int limit;
12849
12850     if (appData.debugMode)
12851         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12852                 target, currentMove, forwardMostMove);
12853
12854     if (gameMode == EditPosition)
12855       return;
12856
12857     if (gameMode == PlayFromGameFile && !pausing)
12858       PauseEvent();
12859
12860     if (gameMode == IcsExamining && pausing)
12861       limit = pauseExamForwardMostMove;
12862     else
12863       limit = forwardMostMove;
12864
12865     if (target > limit) target = limit;
12866
12867     if (target > 0 && moveList[target - 1][0]) {
12868         int fromX, fromY, toX, toY;
12869         toX = moveList[target - 1][2] - AAA;
12870         toY = moveList[target - 1][3] - ONE;
12871         if (moveList[target - 1][1] == '@') {
12872             if (appData.highlightLastMove) {
12873                 SetHighlights(-1, -1, toX, toY);
12874             }
12875         } else {
12876             fromX = moveList[target - 1][0] - AAA;
12877             fromY = moveList[target - 1][1] - ONE;
12878             if (target == currentMove + 1) {
12879                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12880             }
12881             if (appData.highlightLastMove) {
12882                 SetHighlights(fromX, fromY, toX, toY);
12883             }
12884         }
12885     }
12886     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12887         gameMode == Training || gameMode == PlayFromGameFile ||
12888         gameMode == AnalyzeFile) {
12889         while (currentMove < target) {
12890             SendMoveToProgram(currentMove++, &first);
12891         }
12892     } else {
12893         currentMove = target;
12894     }
12895
12896     if (gameMode == EditGame || gameMode == EndOfGame) {
12897         whiteTimeRemaining = timeRemaining[0][currentMove];
12898         blackTimeRemaining = timeRemaining[1][currentMove];
12899     }
12900     DisplayBothClocks();
12901     DisplayMove(currentMove - 1);
12902     DrawPosition(FALSE, boards[currentMove]);
12903     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12904     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12905         DisplayComment(currentMove - 1, commentList[currentMove]);
12906     }
12907 }
12908
12909
12910 void
12911 ForwardEvent()
12912 {
12913     if (gameMode == IcsExamining && !pausing) {
12914         SendToICS(ics_prefix);
12915         SendToICS("forward\n");
12916     } else {
12917         ForwardInner(currentMove + 1);
12918     }
12919 }
12920
12921 void
12922 ToEndEvent()
12923 {
12924     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12925         /* to optimze, we temporarily turn off analysis mode while we feed
12926          * the remaining moves to the engine. Otherwise we get analysis output
12927          * after each move.
12928          */
12929         if (first.analysisSupport) {
12930           SendToProgram("exit\nforce\n", &first);
12931           first.analyzing = FALSE;
12932         }
12933     }
12934
12935     if (gameMode == IcsExamining && !pausing) {
12936         SendToICS(ics_prefix);
12937         SendToICS("forward 999999\n");
12938     } else {
12939         ForwardInner(forwardMostMove);
12940     }
12941
12942     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12943         /* we have fed all the moves, so reactivate analysis mode */
12944         SendToProgram("analyze\n", &first);
12945         first.analyzing = TRUE;
12946         /*first.maybeThinking = TRUE;*/
12947         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12948     }
12949 }
12950
12951 void
12952 BackwardInner(target)
12953      int target;
12954 {
12955     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12956
12957     if (appData.debugMode)
12958         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12959                 target, currentMove, forwardMostMove);
12960
12961     if (gameMode == EditPosition) return;
12962     if (currentMove <= backwardMostMove) {
12963         ClearHighlights();
12964         DrawPosition(full_redraw, boards[currentMove]);
12965         return;
12966     }
12967     if (gameMode == PlayFromGameFile && !pausing)
12968       PauseEvent();
12969
12970     if (moveList[target][0]) {
12971         int fromX, fromY, toX, toY;
12972         toX = moveList[target][2] - AAA;
12973         toY = moveList[target][3] - ONE;
12974         if (moveList[target][1] == '@') {
12975             if (appData.highlightLastMove) {
12976                 SetHighlights(-1, -1, toX, toY);
12977             }
12978         } else {
12979             fromX = moveList[target][0] - AAA;
12980             fromY = moveList[target][1] - ONE;
12981             if (target == currentMove - 1) {
12982                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12983             }
12984             if (appData.highlightLastMove) {
12985                 SetHighlights(fromX, fromY, toX, toY);
12986             }
12987         }
12988     }
12989     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12990         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12991         while (currentMove > target) {
12992             SendToProgram("undo\n", &first);
12993             currentMove--;
12994         }
12995     } else {
12996         currentMove = target;
12997     }
12998
12999     if (gameMode == EditGame || gameMode == EndOfGame) {
13000         whiteTimeRemaining = timeRemaining[0][currentMove];
13001         blackTimeRemaining = timeRemaining[1][currentMove];
13002     }
13003     DisplayBothClocks();
13004     DisplayMove(currentMove - 1);
13005     DrawPosition(full_redraw, boards[currentMove]);
13006     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13007     // [HGM] PV info: routine tests if comment empty
13008     DisplayComment(currentMove - 1, commentList[currentMove]);
13009 }
13010
13011 void
13012 BackwardEvent()
13013 {
13014     if (gameMode == IcsExamining && !pausing) {
13015         SendToICS(ics_prefix);
13016         SendToICS("backward\n");
13017     } else {
13018         BackwardInner(currentMove - 1);
13019     }
13020 }
13021
13022 void
13023 ToStartEvent()
13024 {
13025     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13026         /* to optimize, we temporarily turn off analysis mode while we undo
13027          * all the moves. Otherwise we get analysis output after each undo.
13028          */
13029         if (first.analysisSupport) {
13030           SendToProgram("exit\nforce\n", &first);
13031           first.analyzing = FALSE;
13032         }
13033     }
13034
13035     if (gameMode == IcsExamining && !pausing) {
13036         SendToICS(ics_prefix);
13037         SendToICS("backward 999999\n");
13038     } else {
13039         BackwardInner(backwardMostMove);
13040     }
13041
13042     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13043         /* we have fed all the moves, so reactivate analysis mode */
13044         SendToProgram("analyze\n", &first);
13045         first.analyzing = TRUE;
13046         /*first.maybeThinking = TRUE;*/
13047         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13048     }
13049 }
13050
13051 void
13052 ToNrEvent(int to)
13053 {
13054   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13055   if (to >= forwardMostMove) to = forwardMostMove;
13056   if (to <= backwardMostMove) to = backwardMostMove;
13057   if (to < currentMove) {
13058     BackwardInner(to);
13059   } else {
13060     ForwardInner(to);
13061   }
13062 }
13063
13064 void
13065 RevertEvent(Boolean annotate)
13066 {
13067     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13068         return;
13069     }
13070     if (gameMode != IcsExamining) {
13071         DisplayError(_("You are not examining a game"), 0);
13072         return;
13073     }
13074     if (pausing) {
13075         DisplayError(_("You can't revert while pausing"), 0);
13076         return;
13077     }
13078     SendToICS(ics_prefix);
13079     SendToICS("revert\n");
13080 }
13081
13082 void
13083 RetractMoveEvent()
13084 {
13085     switch (gameMode) {
13086       case MachinePlaysWhite:
13087       case MachinePlaysBlack:
13088         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13089             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13090             return;
13091         }
13092         if (forwardMostMove < 2) return;
13093         currentMove = forwardMostMove = forwardMostMove - 2;
13094         whiteTimeRemaining = timeRemaining[0][currentMove];
13095         blackTimeRemaining = timeRemaining[1][currentMove];
13096         DisplayBothClocks();
13097         DisplayMove(currentMove - 1);
13098         ClearHighlights();/*!! could figure this out*/
13099         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13100         SendToProgram("remove\n", &first);
13101         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13102         break;
13103
13104       case BeginningOfGame:
13105       default:
13106         break;
13107
13108       case IcsPlayingWhite:
13109       case IcsPlayingBlack:
13110         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13111             SendToICS(ics_prefix);
13112             SendToICS("takeback 2\n");
13113         } else {
13114             SendToICS(ics_prefix);
13115             SendToICS("takeback 1\n");
13116         }
13117         break;
13118     }
13119 }
13120
13121 void
13122 MoveNowEvent()
13123 {
13124     ChessProgramState *cps;
13125
13126     switch (gameMode) {
13127       case MachinePlaysWhite:
13128         if (!WhiteOnMove(forwardMostMove)) {
13129             DisplayError(_("It is your turn"), 0);
13130             return;
13131         }
13132         cps = &first;
13133         break;
13134       case MachinePlaysBlack:
13135         if (WhiteOnMove(forwardMostMove)) {
13136             DisplayError(_("It is your turn"), 0);
13137             return;
13138         }
13139         cps = &first;
13140         break;
13141       case TwoMachinesPlay:
13142         if (WhiteOnMove(forwardMostMove) ==
13143             (first.twoMachinesColor[0] == 'w')) {
13144             cps = &first;
13145         } else {
13146             cps = &second;
13147         }
13148         break;
13149       case BeginningOfGame:
13150       default:
13151         return;
13152     }
13153     SendToProgram("?\n", cps);
13154 }
13155
13156 void
13157 TruncateGameEvent()
13158 {
13159     EditGameEvent();
13160     if (gameMode != EditGame) return;
13161     TruncateGame();
13162 }
13163
13164 void
13165 TruncateGame()
13166 {
13167     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13168     if (forwardMostMove > currentMove) {
13169         if (gameInfo.resultDetails != NULL) {
13170             free(gameInfo.resultDetails);
13171             gameInfo.resultDetails = NULL;
13172             gameInfo.result = GameUnfinished;
13173         }
13174         forwardMostMove = currentMove;
13175         HistorySet(parseList, backwardMostMove, forwardMostMove,
13176                    currentMove-1);
13177     }
13178 }
13179
13180 void
13181 HintEvent()
13182 {
13183     if (appData.noChessProgram) return;
13184     switch (gameMode) {
13185       case MachinePlaysWhite:
13186         if (WhiteOnMove(forwardMostMove)) {
13187             DisplayError(_("Wait until your turn"), 0);
13188             return;
13189         }
13190         break;
13191       case BeginningOfGame:
13192       case MachinePlaysBlack:
13193         if (!WhiteOnMove(forwardMostMove)) {
13194             DisplayError(_("Wait until your turn"), 0);
13195             return;
13196         }
13197         break;
13198       default:
13199         DisplayError(_("No hint available"), 0);
13200         return;
13201     }
13202     SendToProgram("hint\n", &first);
13203     hintRequested = TRUE;
13204 }
13205
13206 void
13207 BookEvent()
13208 {
13209     if (appData.noChessProgram) return;
13210     switch (gameMode) {
13211       case MachinePlaysWhite:
13212         if (WhiteOnMove(forwardMostMove)) {
13213             DisplayError(_("Wait until your turn"), 0);
13214             return;
13215         }
13216         break;
13217       case BeginningOfGame:
13218       case MachinePlaysBlack:
13219         if (!WhiteOnMove(forwardMostMove)) {
13220             DisplayError(_("Wait until your turn"), 0);
13221             return;
13222         }
13223         break;
13224       case EditPosition:
13225         EditPositionDone(TRUE);
13226         break;
13227       case TwoMachinesPlay:
13228         return;
13229       default:
13230         break;
13231     }
13232     SendToProgram("bk\n", &first);
13233     bookOutput[0] = NULLCHAR;
13234     bookRequested = TRUE;
13235 }
13236
13237 void
13238 AboutGameEvent()
13239 {
13240     char *tags = PGNTags(&gameInfo);
13241     TagsPopUp(tags, CmailMsg());
13242     free(tags);
13243 }
13244
13245 /* end button procedures */
13246
13247 void
13248 PrintPosition(fp, move)
13249      FILE *fp;
13250      int move;
13251 {
13252     int i, j;
13253
13254     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13255         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13256             char c = PieceToChar(boards[move][i][j]);
13257             fputc(c == 'x' ? '.' : c, fp);
13258             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13259         }
13260     }
13261     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13262       fprintf(fp, "white to play\n");
13263     else
13264       fprintf(fp, "black to play\n");
13265 }
13266
13267 void
13268 PrintOpponents(fp)
13269      FILE *fp;
13270 {
13271     if (gameInfo.white != NULL) {
13272         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13273     } else {
13274         fprintf(fp, "\n");
13275     }
13276 }
13277
13278 /* Find last component of program's own name, using some heuristics */
13279 void
13280 TidyProgramName(prog, host, buf)
13281      char *prog, *host, buf[MSG_SIZ];
13282 {
13283     char *p, *q;
13284     int local = (strcmp(host, "localhost") == 0);
13285     while (!local && (p = strchr(prog, ';')) != NULL) {
13286         p++;
13287         while (*p == ' ') p++;
13288         prog = p;
13289     }
13290     if (*prog == '"' || *prog == '\'') {
13291         q = strchr(prog + 1, *prog);
13292     } else {
13293         q = strchr(prog, ' ');
13294     }
13295     if (q == NULL) q = prog + strlen(prog);
13296     p = q;
13297     while (p >= prog && *p != '/' && *p != '\\') p--;
13298     p++;
13299     if(p == prog && *p == '"') p++;
13300     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13301     memcpy(buf, p, q - p);
13302     buf[q - p] = NULLCHAR;
13303     if (!local) {
13304         strcat(buf, "@");
13305         strcat(buf, host);
13306     }
13307 }
13308
13309 char *
13310 TimeControlTagValue()
13311 {
13312     char buf[MSG_SIZ];
13313     if (!appData.clockMode) {
13314       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13315     } else if (movesPerSession > 0) {
13316       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13317     } else if (timeIncrement == 0) {
13318       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13319     } else {
13320       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13321     }
13322     return StrSave(buf);
13323 }
13324
13325 void
13326 SetGameInfo()
13327 {
13328     /* This routine is used only for certain modes */
13329     VariantClass v = gameInfo.variant;
13330     ChessMove r = GameUnfinished;
13331     char *p = NULL;
13332
13333     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13334         r = gameInfo.result;
13335         p = gameInfo.resultDetails;
13336         gameInfo.resultDetails = NULL;
13337     }
13338     ClearGameInfo(&gameInfo);
13339     gameInfo.variant = v;
13340
13341     switch (gameMode) {
13342       case MachinePlaysWhite:
13343         gameInfo.event = StrSave( appData.pgnEventHeader );
13344         gameInfo.site = StrSave(HostName());
13345         gameInfo.date = PGNDate();
13346         gameInfo.round = StrSave("-");
13347         gameInfo.white = StrSave(first.tidy);
13348         gameInfo.black = StrSave(UserName());
13349         gameInfo.timeControl = TimeControlTagValue();
13350         break;
13351
13352       case MachinePlaysBlack:
13353         gameInfo.event = StrSave( appData.pgnEventHeader );
13354         gameInfo.site = StrSave(HostName());
13355         gameInfo.date = PGNDate();
13356         gameInfo.round = StrSave("-");
13357         gameInfo.white = StrSave(UserName());
13358         gameInfo.black = StrSave(first.tidy);
13359         gameInfo.timeControl = TimeControlTagValue();
13360         break;
13361
13362       case TwoMachinesPlay:
13363         gameInfo.event = StrSave( appData.pgnEventHeader );
13364         gameInfo.site = StrSave(HostName());
13365         gameInfo.date = PGNDate();
13366         if (matchGame > 0) {
13367             char buf[MSG_SIZ];
13368             snprintf(buf, MSG_SIZ, "%d", matchGame);
13369             gameInfo.round = StrSave(buf);
13370         } else {
13371             gameInfo.round = StrSave("-");
13372         }
13373         if (first.twoMachinesColor[0] == 'w') {
13374             gameInfo.white = StrSave(first.tidy);
13375             gameInfo.black = StrSave(second.tidy);
13376         } else {
13377             gameInfo.white = StrSave(second.tidy);
13378             gameInfo.black = StrSave(first.tidy);
13379         }
13380         gameInfo.timeControl = TimeControlTagValue();
13381         break;
13382
13383       case EditGame:
13384         gameInfo.event = StrSave("Edited game");
13385         gameInfo.site = StrSave(HostName());
13386         gameInfo.date = PGNDate();
13387         gameInfo.round = StrSave("-");
13388         gameInfo.white = StrSave("-");
13389         gameInfo.black = StrSave("-");
13390         gameInfo.result = r;
13391         gameInfo.resultDetails = p;
13392         break;
13393
13394       case EditPosition:
13395         gameInfo.event = StrSave("Edited position");
13396         gameInfo.site = StrSave(HostName());
13397         gameInfo.date = PGNDate();
13398         gameInfo.round = StrSave("-");
13399         gameInfo.white = StrSave("-");
13400         gameInfo.black = StrSave("-");
13401         break;
13402
13403       case IcsPlayingWhite:
13404       case IcsPlayingBlack:
13405       case IcsObserving:
13406       case IcsExamining:
13407         break;
13408
13409       case PlayFromGameFile:
13410         gameInfo.event = StrSave("Game from non-PGN file");
13411         gameInfo.site = StrSave(HostName());
13412         gameInfo.date = PGNDate();
13413         gameInfo.round = StrSave("-");
13414         gameInfo.white = StrSave("?");
13415         gameInfo.black = StrSave("?");
13416         break;
13417
13418       default:
13419         break;
13420     }
13421 }
13422
13423 void
13424 ReplaceComment(index, text)
13425      int index;
13426      char *text;
13427 {
13428     int len;
13429     char *p;
13430     float score;
13431
13432     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13433        pvInfoList[index-1].depth == len &&
13434        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13435        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13436     while (*text == '\n') text++;
13437     len = strlen(text);
13438     while (len > 0 && text[len - 1] == '\n') len--;
13439
13440     if (commentList[index] != NULL)
13441       free(commentList[index]);
13442
13443     if (len == 0) {
13444         commentList[index] = NULL;
13445         return;
13446     }
13447   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13448       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13449       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13450     commentList[index] = (char *) malloc(len + 2);
13451     strncpy(commentList[index], text, len);
13452     commentList[index][len] = '\n';
13453     commentList[index][len + 1] = NULLCHAR;
13454   } else {
13455     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13456     char *p;
13457     commentList[index] = (char *) malloc(len + 7);
13458     safeStrCpy(commentList[index], "{\n", 3);
13459     safeStrCpy(commentList[index]+2, text, len+1);
13460     commentList[index][len+2] = NULLCHAR;
13461     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13462     strcat(commentList[index], "\n}\n");
13463   }
13464 }
13465
13466 void
13467 CrushCRs(text)
13468      char *text;
13469 {
13470   char *p = text;
13471   char *q = text;
13472   char ch;
13473
13474   do {
13475     ch = *p++;
13476     if (ch == '\r') continue;
13477     *q++ = ch;
13478   } while (ch != '\0');
13479 }
13480
13481 void
13482 AppendComment(index, text, addBraces)
13483      int index;
13484      char *text;
13485      Boolean addBraces; // [HGM] braces: tells if we should add {}
13486 {
13487     int oldlen, len;
13488     char *old;
13489
13490 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13491     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13492
13493     CrushCRs(text);
13494     while (*text == '\n') text++;
13495     len = strlen(text);
13496     while (len > 0 && text[len - 1] == '\n') len--;
13497
13498     if (len == 0) return;
13499
13500     if (commentList[index] != NULL) {
13501         old = commentList[index];
13502         oldlen = strlen(old);
13503         while(commentList[index][oldlen-1] ==  '\n')
13504           commentList[index][--oldlen] = NULLCHAR;
13505         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13506         safeStrCpy(commentList[index], old, oldlen + len + 6);
13507         free(old);
13508         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13509         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13510           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13511           while (*text == '\n') { text++; len--; }
13512           commentList[index][--oldlen] = NULLCHAR;
13513       }
13514         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13515         else          strcat(commentList[index], "\n");
13516         strcat(commentList[index], text);
13517         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13518         else          strcat(commentList[index], "\n");
13519     } else {
13520         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13521         if(addBraces)
13522           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13523         else commentList[index][0] = NULLCHAR;
13524         strcat(commentList[index], text);
13525         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13526         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13527     }
13528 }
13529
13530 static char * FindStr( char * text, char * sub_text )
13531 {
13532     char * result = strstr( text, sub_text );
13533
13534     if( result != NULL ) {
13535         result += strlen( sub_text );
13536     }
13537
13538     return result;
13539 }
13540
13541 /* [AS] Try to extract PV info from PGN comment */
13542 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13543 char *GetInfoFromComment( int index, char * text )
13544 {
13545     char * sep = text, *p;
13546
13547     if( text != NULL && index > 0 ) {
13548         int score = 0;
13549         int depth = 0;
13550         int time = -1, sec = 0, deci;
13551         char * s_eval = FindStr( text, "[%eval " );
13552         char * s_emt = FindStr( text, "[%emt " );
13553
13554         if( s_eval != NULL || s_emt != NULL ) {
13555             /* New style */
13556             char delim;
13557
13558             if( s_eval != NULL ) {
13559                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13560                     return text;
13561                 }
13562
13563                 if( delim != ']' ) {
13564                     return text;
13565                 }
13566             }
13567
13568             if( s_emt != NULL ) {
13569             }
13570                 return text;
13571         }
13572         else {
13573             /* We expect something like: [+|-]nnn.nn/dd */
13574             int score_lo = 0;
13575
13576             if(*text != '{') return text; // [HGM] braces: must be normal comment
13577
13578             sep = strchr( text, '/' );
13579             if( sep == NULL || sep < (text+4) ) {
13580                 return text;
13581             }
13582
13583             p = text;
13584             if(p[1] == '(') { // comment starts with PV
13585                p = strchr(p, ')'); // locate end of PV
13586                if(p == NULL || sep < p+5) return text;
13587                // at this point we have something like "{(.*) +0.23/6 ..."
13588                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13589                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13590                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13591             }
13592             time = -1; sec = -1; deci = -1;
13593             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13594                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13595                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13596                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13597                 return text;
13598             }
13599
13600             if( score_lo < 0 || score_lo >= 100 ) {
13601                 return text;
13602             }
13603
13604             if(sec >= 0) time = 600*time + 10*sec; else
13605             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13606
13607             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13608
13609             /* [HGM] PV time: now locate end of PV info */
13610             while( *++sep >= '0' && *sep <= '9'); // strip depth
13611             if(time >= 0)
13612             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13613             if(sec >= 0)
13614             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13615             if(deci >= 0)
13616             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13617             while(*sep == ' ') sep++;
13618         }
13619
13620         if( depth <= 0 ) {
13621             return text;
13622         }
13623
13624         if( time < 0 ) {
13625             time = -1;
13626         }
13627
13628         pvInfoList[index-1].depth = depth;
13629         pvInfoList[index-1].score = score;
13630         pvInfoList[index-1].time  = 10*time; // centi-sec
13631         if(*sep == '}') *sep = 0; else *--sep = '{';
13632         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13633     }
13634     return sep;
13635 }
13636
13637 void
13638 SendToProgram(message, cps)
13639      char *message;
13640      ChessProgramState *cps;
13641 {
13642     int count, outCount, error;
13643     char buf[MSG_SIZ];
13644
13645     if (cps->pr == NULL) return;
13646     Attention(cps);
13647
13648     if (appData.debugMode) {
13649         TimeMark now;
13650         GetTimeMark(&now);
13651         fprintf(debugFP, "%ld >%-6s: %s",
13652                 SubtractTimeMarks(&now, &programStartTime),
13653                 cps->which, message);
13654     }
13655
13656     count = strlen(message);
13657     outCount = OutputToProcess(cps->pr, message, count, &error);
13658     if (outCount < count && !exiting
13659                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13660       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13661         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13662             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13663                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13664                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13665             } else {
13666                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13667             }
13668             gameInfo.resultDetails = StrSave(buf);
13669         }
13670         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13671     }
13672 }
13673
13674 void
13675 ReceiveFromProgram(isr, closure, message, count, error)
13676      InputSourceRef isr;
13677      VOIDSTAR closure;
13678      char *message;
13679      int count;
13680      int error;
13681 {
13682     char *end_str;
13683     char buf[MSG_SIZ];
13684     ChessProgramState *cps = (ChessProgramState *)closure;
13685
13686     if (isr != cps->isr) return; /* Killed intentionally */
13687     if (count <= 0) {
13688         if (count == 0) {
13689             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13690                     _(cps->which), cps->program);
13691         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13692                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13693                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13694                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13695                 } else {
13696                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13697                 }
13698                 gameInfo.resultDetails = StrSave(buf);
13699             }
13700             RemoveInputSource(cps->isr);
13701             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13702         } else {
13703             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13704                     _(cps->which), cps->program);
13705             RemoveInputSource(cps->isr);
13706
13707             /* [AS] Program is misbehaving badly... kill it */
13708             if( count == -2 ) {
13709                 DestroyChildProcess( cps->pr, 9 );
13710                 cps->pr = NoProc;
13711             }
13712
13713             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13714         }
13715         return;
13716     }
13717
13718     if ((end_str = strchr(message, '\r')) != NULL)
13719       *end_str = NULLCHAR;
13720     if ((end_str = strchr(message, '\n')) != NULL)
13721       *end_str = NULLCHAR;
13722
13723     if (appData.debugMode) {
13724         TimeMark now; int print = 1;
13725         char *quote = ""; char c; int i;
13726
13727         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13728                 char start = message[0];
13729                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13730                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13731                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13732                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13733                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13734                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13735                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13736                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13737                    sscanf(message, "hint: %c", &c)!=1 && 
13738                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13739                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13740                     print = (appData.engineComments >= 2);
13741                 }
13742                 message[0] = start; // restore original message
13743         }
13744         if(print) {
13745                 GetTimeMark(&now);
13746                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13747                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13748                         quote,
13749                         message);
13750         }
13751     }
13752
13753     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13754     if (appData.icsEngineAnalyze) {
13755         if (strstr(message, "whisper") != NULL ||
13756              strstr(message, "kibitz") != NULL ||
13757             strstr(message, "tellics") != NULL) return;
13758     }
13759
13760     HandleMachineMove(message, cps);
13761 }
13762
13763
13764 void
13765 SendTimeControl(cps, mps, tc, inc, sd, st)
13766      ChessProgramState *cps;
13767      int mps, inc, sd, st;
13768      long tc;
13769 {
13770     char buf[MSG_SIZ];
13771     int seconds;
13772
13773     if( timeControl_2 > 0 ) {
13774         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13775             tc = timeControl_2;
13776         }
13777     }
13778     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13779     inc /= cps->timeOdds;
13780     st  /= cps->timeOdds;
13781
13782     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13783
13784     if (st > 0) {
13785       /* Set exact time per move, normally using st command */
13786       if (cps->stKludge) {
13787         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13788         seconds = st % 60;
13789         if (seconds == 0) {
13790           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13791         } else {
13792           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13793         }
13794       } else {
13795         snprintf(buf, MSG_SIZ, "st %d\n", st);
13796       }
13797     } else {
13798       /* Set conventional or incremental time control, using level command */
13799       if (seconds == 0) {
13800         /* Note old gnuchess bug -- minutes:seconds used to not work.
13801            Fixed in later versions, but still avoid :seconds
13802            when seconds is 0. */
13803         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13804       } else {
13805         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13806                  seconds, inc/1000.);
13807       }
13808     }
13809     SendToProgram(buf, cps);
13810
13811     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13812     /* Orthogonally, limit search to given depth */
13813     if (sd > 0) {
13814       if (cps->sdKludge) {
13815         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13816       } else {
13817         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13818       }
13819       SendToProgram(buf, cps);
13820     }
13821
13822     if(cps->nps >= 0) { /* [HGM] nps */
13823         if(cps->supportsNPS == FALSE)
13824           cps->nps = -1; // don't use if engine explicitly says not supported!
13825         else {
13826           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13827           SendToProgram(buf, cps);
13828         }
13829     }
13830 }
13831
13832 ChessProgramState *WhitePlayer()
13833 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13834 {
13835     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13836        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13837         return &second;
13838     return &first;
13839 }
13840
13841 void
13842 SendTimeRemaining(cps, machineWhite)
13843      ChessProgramState *cps;
13844      int /*boolean*/ machineWhite;
13845 {
13846     char message[MSG_SIZ];
13847     long time, otime;
13848
13849     /* Note: this routine must be called when the clocks are stopped
13850        or when they have *just* been set or switched; otherwise
13851        it will be off by the time since the current tick started.
13852     */
13853     if (machineWhite) {
13854         time = whiteTimeRemaining / 10;
13855         otime = blackTimeRemaining / 10;
13856     } else {
13857         time = blackTimeRemaining / 10;
13858         otime = whiteTimeRemaining / 10;
13859     }
13860     /* [HGM] translate opponent's time by time-odds factor */
13861     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13862     if (appData.debugMode) {
13863         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13864     }
13865
13866     if (time <= 0) time = 1;
13867     if (otime <= 0) otime = 1;
13868
13869     snprintf(message, MSG_SIZ, "time %ld\n", time);
13870     SendToProgram(message, cps);
13871
13872     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13873     SendToProgram(message, cps);
13874 }
13875
13876 int
13877 BoolFeature(p, name, loc, cps)
13878      char **p;
13879      char *name;
13880      int *loc;
13881      ChessProgramState *cps;
13882 {
13883   char buf[MSG_SIZ];
13884   int len = strlen(name);
13885   int val;
13886
13887   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13888     (*p) += len + 1;
13889     sscanf(*p, "%d", &val);
13890     *loc = (val != 0);
13891     while (**p && **p != ' ')
13892       (*p)++;
13893     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13894     SendToProgram(buf, cps);
13895     return TRUE;
13896   }
13897   return FALSE;
13898 }
13899
13900 int
13901 IntFeature(p, name, loc, cps)
13902      char **p;
13903      char *name;
13904      int *loc;
13905      ChessProgramState *cps;
13906 {
13907   char buf[MSG_SIZ];
13908   int len = strlen(name);
13909   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13910     (*p) += len + 1;
13911     sscanf(*p, "%d", loc);
13912     while (**p && **p != ' ') (*p)++;
13913     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13914     SendToProgram(buf, cps);
13915     return TRUE;
13916   }
13917   return FALSE;
13918 }
13919
13920 int
13921 StringFeature(p, name, loc, cps)
13922      char **p;
13923      char *name;
13924      char loc[];
13925      ChessProgramState *cps;
13926 {
13927   char buf[MSG_SIZ];
13928   int len = strlen(name);
13929   if (strncmp((*p), name, len) == 0
13930       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13931     (*p) += len + 2;
13932     sscanf(*p, "%[^\"]", loc);
13933     while (**p && **p != '\"') (*p)++;
13934     if (**p == '\"') (*p)++;
13935     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13936     SendToProgram(buf, cps);
13937     return TRUE;
13938   }
13939   return FALSE;
13940 }
13941
13942 int
13943 ParseOption(Option *opt, ChessProgramState *cps)
13944 // [HGM] options: process the string that defines an engine option, and determine
13945 // name, type, default value, and allowed value range
13946 {
13947         char *p, *q, buf[MSG_SIZ];
13948         int n, min = (-1)<<31, max = 1<<31, def;
13949
13950         if(p = strstr(opt->name, " -spin ")) {
13951             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13952             if(max < min) max = min; // enforce consistency
13953             if(def < min) def = min;
13954             if(def > max) def = max;
13955             opt->value = def;
13956             opt->min = min;
13957             opt->max = max;
13958             opt->type = Spin;
13959         } else if((p = strstr(opt->name, " -slider "))) {
13960             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13961             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13962             if(max < min) max = min; // enforce consistency
13963             if(def < min) def = min;
13964             if(def > max) def = max;
13965             opt->value = def;
13966             opt->min = min;
13967             opt->max = max;
13968             opt->type = Spin; // Slider;
13969         } else if((p = strstr(opt->name, " -string "))) {
13970             opt->textValue = p+9;
13971             opt->type = TextBox;
13972         } else if((p = strstr(opt->name, " -file "))) {
13973             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13974             opt->textValue = p+7;
13975             opt->type = FileName; // FileName;
13976         } else if((p = strstr(opt->name, " -path "))) {
13977             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13978             opt->textValue = p+7;
13979             opt->type = PathName; // PathName;
13980         } else if(p = strstr(opt->name, " -check ")) {
13981             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13982             opt->value = (def != 0);
13983             opt->type = CheckBox;
13984         } else if(p = strstr(opt->name, " -combo ")) {
13985             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13986             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13987             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13988             opt->value = n = 0;
13989             while(q = StrStr(q, " /// ")) {
13990                 n++; *q = 0;    // count choices, and null-terminate each of them
13991                 q += 5;
13992                 if(*q == '*') { // remember default, which is marked with * prefix
13993                     q++;
13994                     opt->value = n;
13995                 }
13996                 cps->comboList[cps->comboCnt++] = q;
13997             }
13998             cps->comboList[cps->comboCnt++] = NULL;
13999             opt->max = n + 1;
14000             opt->type = ComboBox;
14001         } else if(p = strstr(opt->name, " -button")) {
14002             opt->type = Button;
14003         } else if(p = strstr(opt->name, " -save")) {
14004             opt->type = SaveButton;
14005         } else return FALSE;
14006         *p = 0; // terminate option name
14007         // now look if the command-line options define a setting for this engine option.
14008         if(cps->optionSettings && cps->optionSettings[0])
14009             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14010         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14011           snprintf(buf, MSG_SIZ, "option %s", p);
14012                 if(p = strstr(buf, ",")) *p = 0;
14013                 if(q = strchr(buf, '=')) switch(opt->type) {
14014                     case ComboBox:
14015                         for(n=0; n<opt->max; n++)
14016                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14017                         break;
14018                     case TextBox:
14019                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14020                         break;
14021                     case Spin:
14022                     case CheckBox:
14023                         opt->value = atoi(q+1);
14024                     default:
14025                         break;
14026                 }
14027                 strcat(buf, "\n");
14028                 SendToProgram(buf, cps);
14029         }
14030         return TRUE;
14031 }
14032
14033 void
14034 FeatureDone(cps, val)
14035      ChessProgramState* cps;
14036      int val;
14037 {
14038   DelayedEventCallback cb = GetDelayedEvent();
14039   if ((cb == InitBackEnd3 && cps == &first) ||
14040       (cb == SettingsMenuIfReady && cps == &second) ||
14041       (cb == TwoMachinesEventIfReady && cps == &second)) {
14042     CancelDelayedEvent();
14043     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14044   }
14045   cps->initDone = val;
14046 }
14047
14048 /* Parse feature command from engine */
14049 void
14050 ParseFeatures(args, cps)
14051      char* args;
14052      ChessProgramState *cps;
14053 {
14054   char *p = args;
14055   char *q;
14056   int val;
14057   char buf[MSG_SIZ];
14058
14059   for (;;) {
14060     while (*p == ' ') p++;
14061     if (*p == NULLCHAR) return;
14062
14063     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14064     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14065     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14066     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14067     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14068     if (BoolFeature(&p, "reuse", &val, cps)) {
14069       /* Engine can disable reuse, but can't enable it if user said no */
14070       if (!val) cps->reuse = FALSE;
14071       continue;
14072     }
14073     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14074     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14075       if (gameMode == TwoMachinesPlay) {
14076         DisplayTwoMachinesTitle();
14077       } else {
14078         DisplayTitle("");
14079       }
14080       continue;
14081     }
14082     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14083     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14084     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14085     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14086     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14087     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14088     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14089     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14090     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14091     if (IntFeature(&p, "done", &val, cps)) {
14092       FeatureDone(cps, val);
14093       continue;
14094     }
14095     /* Added by Tord: */
14096     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14097     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14098     /* End of additions by Tord */
14099
14100     /* [HGM] added features: */
14101     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14102     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14103     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14104     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14105     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14106     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14107     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14108         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14109           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14110             SendToProgram(buf, cps);
14111             continue;
14112         }
14113         if(cps->nrOptions >= MAX_OPTIONS) {
14114             cps->nrOptions--;
14115             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14116             DisplayError(buf, 0);
14117         }
14118         continue;
14119     }
14120     /* End of additions by HGM */
14121
14122     /* unknown feature: complain and skip */
14123     q = p;
14124     while (*q && *q != '=') q++;
14125     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14126     SendToProgram(buf, cps);
14127     p = q;
14128     if (*p == '=') {
14129       p++;
14130       if (*p == '\"') {
14131         p++;
14132         while (*p && *p != '\"') p++;
14133         if (*p == '\"') p++;
14134       } else {
14135         while (*p && *p != ' ') p++;
14136       }
14137     }
14138   }
14139
14140 }
14141
14142 void
14143 PeriodicUpdatesEvent(newState)
14144      int newState;
14145 {
14146     if (newState == appData.periodicUpdates)
14147       return;
14148
14149     appData.periodicUpdates=newState;
14150
14151     /* Display type changes, so update it now */
14152 //    DisplayAnalysis();
14153
14154     /* Get the ball rolling again... */
14155     if (newState) {
14156         AnalysisPeriodicEvent(1);
14157         StartAnalysisClock();
14158     }
14159 }
14160
14161 void
14162 PonderNextMoveEvent(newState)
14163      int newState;
14164 {
14165     if (newState == appData.ponderNextMove) return;
14166     if (gameMode == EditPosition) EditPositionDone(TRUE);
14167     if (newState) {
14168         SendToProgram("hard\n", &first);
14169         if (gameMode == TwoMachinesPlay) {
14170             SendToProgram("hard\n", &second);
14171         }
14172     } else {
14173         SendToProgram("easy\n", &first);
14174         thinkOutput[0] = NULLCHAR;
14175         if (gameMode == TwoMachinesPlay) {
14176             SendToProgram("easy\n", &second);
14177         }
14178     }
14179     appData.ponderNextMove = newState;
14180 }
14181
14182 void
14183 NewSettingEvent(option, feature, command, value)
14184      char *command;
14185      int option, value, *feature;
14186 {
14187     char buf[MSG_SIZ];
14188
14189     if (gameMode == EditPosition) EditPositionDone(TRUE);
14190     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14191     if(feature == NULL || *feature) SendToProgram(buf, &first);
14192     if (gameMode == TwoMachinesPlay) {
14193         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14194     }
14195 }
14196
14197 void
14198 ShowThinkingEvent()
14199 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14200 {
14201     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14202     int newState = appData.showThinking
14203         // [HGM] thinking: other features now need thinking output as well
14204         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14205
14206     if (oldState == newState) return;
14207     oldState = newState;
14208     if (gameMode == EditPosition) EditPositionDone(TRUE);
14209     if (oldState) {
14210         SendToProgram("post\n", &first);
14211         if (gameMode == TwoMachinesPlay) {
14212             SendToProgram("post\n", &second);
14213         }
14214     } else {
14215         SendToProgram("nopost\n", &first);
14216         thinkOutput[0] = NULLCHAR;
14217         if (gameMode == TwoMachinesPlay) {
14218             SendToProgram("nopost\n", &second);
14219         }
14220     }
14221 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14222 }
14223
14224 void
14225 AskQuestionEvent(title, question, replyPrefix, which)
14226      char *title; char *question; char *replyPrefix; char *which;
14227 {
14228   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14229   if (pr == NoProc) return;
14230   AskQuestion(title, question, replyPrefix, pr);
14231 }
14232
14233 void
14234 DisplayMove(moveNumber)
14235      int moveNumber;
14236 {
14237     char message[MSG_SIZ];
14238     char res[MSG_SIZ];
14239     char cpThinkOutput[MSG_SIZ];
14240
14241     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14242
14243     if (moveNumber == forwardMostMove - 1 ||
14244         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14245
14246         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14247
14248         if (strchr(cpThinkOutput, '\n')) {
14249             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14250         }
14251     } else {
14252         *cpThinkOutput = NULLCHAR;
14253     }
14254
14255     /* [AS] Hide thinking from human user */
14256     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14257         *cpThinkOutput = NULLCHAR;
14258         if( thinkOutput[0] != NULLCHAR ) {
14259             int i;
14260
14261             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14262                 cpThinkOutput[i] = '.';
14263             }
14264             cpThinkOutput[i] = NULLCHAR;
14265             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14266         }
14267     }
14268
14269     if (moveNumber == forwardMostMove - 1 &&
14270         gameInfo.resultDetails != NULL) {
14271         if (gameInfo.resultDetails[0] == NULLCHAR) {
14272           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14273         } else {
14274           snprintf(res, MSG_SIZ, " {%s} %s",
14275                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14276         }
14277     } else {
14278         res[0] = NULLCHAR;
14279     }
14280
14281     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14282         DisplayMessage(res, cpThinkOutput);
14283     } else {
14284       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14285                 WhiteOnMove(moveNumber) ? " " : ".. ",
14286                 parseList[moveNumber], res);
14287         DisplayMessage(message, cpThinkOutput);
14288     }
14289 }
14290
14291 void
14292 DisplayComment(moveNumber, text)
14293      int moveNumber;
14294      char *text;
14295 {
14296     char title[MSG_SIZ];
14297     char buf[8000]; // comment can be long!
14298     int score, depth;
14299
14300     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14301       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14302     } else {
14303       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14304               WhiteOnMove(moveNumber) ? " " : ".. ",
14305               parseList[moveNumber]);
14306     }
14307     // [HGM] PV info: display PV info together with (or as) comment
14308     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14309       if(text == NULL) text = "";
14310       score = pvInfoList[moveNumber].score;
14311       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14312               depth, (pvInfoList[moveNumber].time+50)/100, text);
14313       text = buf;
14314     }
14315     if (text != NULL && (appData.autoDisplayComment || commentUp))
14316         CommentPopUp(title, text);
14317 }
14318
14319 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14320  * might be busy thinking or pondering.  It can be omitted if your
14321  * gnuchess is configured to stop thinking immediately on any user
14322  * input.  However, that gnuchess feature depends on the FIONREAD
14323  * ioctl, which does not work properly on some flavors of Unix.
14324  */
14325 void
14326 Attention(cps)
14327      ChessProgramState *cps;
14328 {
14329 #if ATTENTION
14330     if (!cps->useSigint) return;
14331     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14332     switch (gameMode) {
14333       case MachinePlaysWhite:
14334       case MachinePlaysBlack:
14335       case TwoMachinesPlay:
14336       case IcsPlayingWhite:
14337       case IcsPlayingBlack:
14338       case AnalyzeMode:
14339       case AnalyzeFile:
14340         /* Skip if we know it isn't thinking */
14341         if (!cps->maybeThinking) return;
14342         if (appData.debugMode)
14343           fprintf(debugFP, "Interrupting %s\n", cps->which);
14344         InterruptChildProcess(cps->pr);
14345         cps->maybeThinking = FALSE;
14346         break;
14347       default:
14348         break;
14349     }
14350 #endif /*ATTENTION*/
14351 }
14352
14353 int
14354 CheckFlags()
14355 {
14356     if (whiteTimeRemaining <= 0) {
14357         if (!whiteFlag) {
14358             whiteFlag = TRUE;
14359             if (appData.icsActive) {
14360                 if (appData.autoCallFlag &&
14361                     gameMode == IcsPlayingBlack && !blackFlag) {
14362                   SendToICS(ics_prefix);
14363                   SendToICS("flag\n");
14364                 }
14365             } else {
14366                 if (blackFlag) {
14367                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14368                 } else {
14369                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14370                     if (appData.autoCallFlag) {
14371                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14372                         return TRUE;
14373                     }
14374                 }
14375             }
14376         }
14377     }
14378     if (blackTimeRemaining <= 0) {
14379         if (!blackFlag) {
14380             blackFlag = TRUE;
14381             if (appData.icsActive) {
14382                 if (appData.autoCallFlag &&
14383                     gameMode == IcsPlayingWhite && !whiteFlag) {
14384                   SendToICS(ics_prefix);
14385                   SendToICS("flag\n");
14386                 }
14387             } else {
14388                 if (whiteFlag) {
14389                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14390                 } else {
14391                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14392                     if (appData.autoCallFlag) {
14393                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14394                         return TRUE;
14395                     }
14396                 }
14397             }
14398         }
14399     }
14400     return FALSE;
14401 }
14402
14403 void
14404 CheckTimeControl()
14405 {
14406     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14407         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14408
14409     /*
14410      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14411      */
14412     if ( !WhiteOnMove(forwardMostMove) ) {
14413         /* White made time control */
14414         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14415         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14416         /* [HGM] time odds: correct new time quota for time odds! */
14417                                             / WhitePlayer()->timeOdds;
14418         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14419     } else {
14420         lastBlack -= blackTimeRemaining;
14421         /* Black made time control */
14422         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14423                                             / WhitePlayer()->other->timeOdds;
14424         lastWhite = whiteTimeRemaining;
14425     }
14426 }
14427
14428 void
14429 DisplayBothClocks()
14430 {
14431     int wom = gameMode == EditPosition ?
14432       !blackPlaysFirst : WhiteOnMove(currentMove);
14433     DisplayWhiteClock(whiteTimeRemaining, wom);
14434     DisplayBlackClock(blackTimeRemaining, !wom);
14435 }
14436
14437
14438 /* Timekeeping seems to be a portability nightmare.  I think everyone
14439    has ftime(), but I'm really not sure, so I'm including some ifdefs
14440    to use other calls if you don't.  Clocks will be less accurate if
14441    you have neither ftime nor gettimeofday.
14442 */
14443
14444 /* VS 2008 requires the #include outside of the function */
14445 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14446 #include <sys/timeb.h>
14447 #endif
14448
14449 /* Get the current time as a TimeMark */
14450 void
14451 GetTimeMark(tm)
14452      TimeMark *tm;
14453 {
14454 #if HAVE_GETTIMEOFDAY
14455
14456     struct timeval timeVal;
14457     struct timezone timeZone;
14458
14459     gettimeofday(&timeVal, &timeZone);
14460     tm->sec = (long) timeVal.tv_sec;
14461     tm->ms = (int) (timeVal.tv_usec / 1000L);
14462
14463 #else /*!HAVE_GETTIMEOFDAY*/
14464 #if HAVE_FTIME
14465
14466 // include <sys/timeb.h> / moved to just above start of function
14467     struct timeb timeB;
14468
14469     ftime(&timeB);
14470     tm->sec = (long) timeB.time;
14471     tm->ms = (int) timeB.millitm;
14472
14473 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14474     tm->sec = (long) time(NULL);
14475     tm->ms = 0;
14476 #endif
14477 #endif
14478 }
14479
14480 /* Return the difference in milliseconds between two
14481    time marks.  We assume the difference will fit in a long!
14482 */
14483 long
14484 SubtractTimeMarks(tm2, tm1)
14485      TimeMark *tm2, *tm1;
14486 {
14487     return 1000L*(tm2->sec - tm1->sec) +
14488            (long) (tm2->ms - tm1->ms);
14489 }
14490
14491
14492 /*
14493  * Code to manage the game clocks.
14494  *
14495  * In tournament play, black starts the clock and then white makes a move.
14496  * We give the human user a slight advantage if he is playing white---the
14497  * clocks don't run until he makes his first move, so it takes zero time.
14498  * Also, we don't account for network lag, so we could get out of sync
14499  * with GNU Chess's clock -- but then, referees are always right.
14500  */
14501
14502 static TimeMark tickStartTM;
14503 static long intendedTickLength;
14504
14505 long
14506 NextTickLength(timeRemaining)
14507      long timeRemaining;
14508 {
14509     long nominalTickLength, nextTickLength;
14510
14511     if (timeRemaining > 0L && timeRemaining <= 10000L)
14512       nominalTickLength = 100L;
14513     else
14514       nominalTickLength = 1000L;
14515     nextTickLength = timeRemaining % nominalTickLength;
14516     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14517
14518     return nextTickLength;
14519 }
14520
14521 /* Adjust clock one minute up or down */
14522 void
14523 AdjustClock(Boolean which, int dir)
14524 {
14525     if(which) blackTimeRemaining += 60000*dir;
14526     else      whiteTimeRemaining += 60000*dir;
14527     DisplayBothClocks();
14528 }
14529
14530 /* Stop clocks and reset to a fresh time control */
14531 void
14532 ResetClocks()
14533 {
14534     (void) StopClockTimer();
14535     if (appData.icsActive) {
14536         whiteTimeRemaining = blackTimeRemaining = 0;
14537     } else if (searchTime) {
14538         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14539         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14540     } else { /* [HGM] correct new time quote for time odds */
14541         whiteTC = blackTC = fullTimeControlString;
14542         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14543         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14544     }
14545     if (whiteFlag || blackFlag) {
14546         DisplayTitle("");
14547         whiteFlag = blackFlag = FALSE;
14548     }
14549     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14550     DisplayBothClocks();
14551 }
14552
14553 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14554
14555 /* Decrement running clock by amount of time that has passed */
14556 void
14557 DecrementClocks()
14558 {
14559     long timeRemaining;
14560     long lastTickLength, fudge;
14561     TimeMark now;
14562
14563     if (!appData.clockMode) return;
14564     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14565
14566     GetTimeMark(&now);
14567
14568     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14569
14570     /* Fudge if we woke up a little too soon */
14571     fudge = intendedTickLength - lastTickLength;
14572     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14573
14574     if (WhiteOnMove(forwardMostMove)) {
14575         if(whiteNPS >= 0) lastTickLength = 0;
14576         timeRemaining = whiteTimeRemaining -= lastTickLength;
14577         if(timeRemaining < 0 && !appData.icsActive) {
14578             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14579             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14580                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14581                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14582             }
14583         }
14584         DisplayWhiteClock(whiteTimeRemaining - fudge,
14585                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14586     } else {
14587         if(blackNPS >= 0) lastTickLength = 0;
14588         timeRemaining = blackTimeRemaining -= lastTickLength;
14589         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14590             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14591             if(suddenDeath) {
14592                 blackStartMove = forwardMostMove;
14593                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14594             }
14595         }
14596         DisplayBlackClock(blackTimeRemaining - fudge,
14597                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14598     }
14599     if (CheckFlags()) return;
14600
14601     tickStartTM = now;
14602     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14603     StartClockTimer(intendedTickLength);
14604
14605     /* if the time remaining has fallen below the alarm threshold, sound the
14606      * alarm. if the alarm has sounded and (due to a takeback or time control
14607      * with increment) the time remaining has increased to a level above the
14608      * threshold, reset the alarm so it can sound again.
14609      */
14610
14611     if (appData.icsActive && appData.icsAlarm) {
14612
14613         /* make sure we are dealing with the user's clock */
14614         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14615                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14616            )) return;
14617
14618         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14619             alarmSounded = FALSE;
14620         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14621             PlayAlarmSound();
14622             alarmSounded = TRUE;
14623         }
14624     }
14625 }
14626
14627
14628 /* A player has just moved, so stop the previously running
14629    clock and (if in clock mode) start the other one.
14630    We redisplay both clocks in case we're in ICS mode, because
14631    ICS gives us an update to both clocks after every move.
14632    Note that this routine is called *after* forwardMostMove
14633    is updated, so the last fractional tick must be subtracted
14634    from the color that is *not* on move now.
14635 */
14636 void
14637 SwitchClocks(int newMoveNr)
14638 {
14639     long lastTickLength;
14640     TimeMark now;
14641     int flagged = FALSE;
14642
14643     GetTimeMark(&now);
14644
14645     if (StopClockTimer() && appData.clockMode) {
14646         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14647         if (!WhiteOnMove(forwardMostMove)) {
14648             if(blackNPS >= 0) lastTickLength = 0;
14649             blackTimeRemaining -= lastTickLength;
14650            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14651 //         if(pvInfoList[forwardMostMove].time == -1)
14652                  pvInfoList[forwardMostMove].time =               // use GUI time
14653                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14654         } else {
14655            if(whiteNPS >= 0) lastTickLength = 0;
14656            whiteTimeRemaining -= lastTickLength;
14657            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14658 //         if(pvInfoList[forwardMostMove].time == -1)
14659                  pvInfoList[forwardMostMove].time =
14660                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14661         }
14662         flagged = CheckFlags();
14663     }
14664     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14665     CheckTimeControl();
14666
14667     if (flagged || !appData.clockMode) return;
14668
14669     switch (gameMode) {
14670       case MachinePlaysBlack:
14671       case MachinePlaysWhite:
14672       case BeginningOfGame:
14673         if (pausing) return;
14674         break;
14675
14676       case EditGame:
14677       case PlayFromGameFile:
14678       case IcsExamining:
14679         return;
14680
14681       default:
14682         break;
14683     }
14684
14685     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14686         if(WhiteOnMove(forwardMostMove))
14687              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14688         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14689     }
14690
14691     tickStartTM = now;
14692     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14693       whiteTimeRemaining : blackTimeRemaining);
14694     StartClockTimer(intendedTickLength);
14695 }
14696
14697
14698 /* Stop both clocks */
14699 void
14700 StopClocks()
14701 {
14702     long lastTickLength;
14703     TimeMark now;
14704
14705     if (!StopClockTimer()) return;
14706     if (!appData.clockMode) return;
14707
14708     GetTimeMark(&now);
14709
14710     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14711     if (WhiteOnMove(forwardMostMove)) {
14712         if(whiteNPS >= 0) lastTickLength = 0;
14713         whiteTimeRemaining -= lastTickLength;
14714         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14715     } else {
14716         if(blackNPS >= 0) lastTickLength = 0;
14717         blackTimeRemaining -= lastTickLength;
14718         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14719     }
14720     CheckFlags();
14721 }
14722
14723 /* Start clock of player on move.  Time may have been reset, so
14724    if clock is already running, stop and restart it. */
14725 void
14726 StartClocks()
14727 {
14728     (void) StopClockTimer(); /* in case it was running already */
14729     DisplayBothClocks();
14730     if (CheckFlags()) return;
14731
14732     if (!appData.clockMode) return;
14733     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14734
14735     GetTimeMark(&tickStartTM);
14736     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14737       whiteTimeRemaining : blackTimeRemaining);
14738
14739    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14740     whiteNPS = blackNPS = -1;
14741     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14742        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14743         whiteNPS = first.nps;
14744     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14745        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14746         blackNPS = first.nps;
14747     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14748         whiteNPS = second.nps;
14749     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14750         blackNPS = second.nps;
14751     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14752
14753     StartClockTimer(intendedTickLength);
14754 }
14755
14756 char *
14757 TimeString(ms)
14758      long ms;
14759 {
14760     long second, minute, hour, day;
14761     char *sign = "";
14762     static char buf[32];
14763
14764     if (ms > 0 && ms <= 9900) {
14765       /* convert milliseconds to tenths, rounding up */
14766       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14767
14768       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14769       return buf;
14770     }
14771
14772     /* convert milliseconds to seconds, rounding up */
14773     /* use floating point to avoid strangeness of integer division
14774        with negative dividends on many machines */
14775     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14776
14777     if (second < 0) {
14778         sign = "-";
14779         second = -second;
14780     }
14781
14782     day = second / (60 * 60 * 24);
14783     second = second % (60 * 60 * 24);
14784     hour = second / (60 * 60);
14785     second = second % (60 * 60);
14786     minute = second / 60;
14787     second = second % 60;
14788
14789     if (day > 0)
14790       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14791               sign, day, hour, minute, second);
14792     else if (hour > 0)
14793       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14794     else
14795       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14796
14797     return buf;
14798 }
14799
14800
14801 /*
14802  * This is necessary because some C libraries aren't ANSI C compliant yet.
14803  */
14804 char *
14805 StrStr(string, match)
14806      char *string, *match;
14807 {
14808     int i, length;
14809
14810     length = strlen(match);
14811
14812     for (i = strlen(string) - length; i >= 0; i--, string++)
14813       if (!strncmp(match, string, length))
14814         return string;
14815
14816     return NULL;
14817 }
14818
14819 char *
14820 StrCaseStr(string, match)
14821      char *string, *match;
14822 {
14823     int i, j, length;
14824
14825     length = strlen(match);
14826
14827     for (i = strlen(string) - length; i >= 0; i--, string++) {
14828         for (j = 0; j < length; j++) {
14829             if (ToLower(match[j]) != ToLower(string[j]))
14830               break;
14831         }
14832         if (j == length) return string;
14833     }
14834
14835     return NULL;
14836 }
14837
14838 #ifndef _amigados
14839 int
14840 StrCaseCmp(s1, s2)
14841      char *s1, *s2;
14842 {
14843     char c1, c2;
14844
14845     for (;;) {
14846         c1 = ToLower(*s1++);
14847         c2 = ToLower(*s2++);
14848         if (c1 > c2) return 1;
14849         if (c1 < c2) return -1;
14850         if (c1 == NULLCHAR) return 0;
14851     }
14852 }
14853
14854
14855 int
14856 ToLower(c)
14857      int c;
14858 {
14859     return isupper(c) ? tolower(c) : c;
14860 }
14861
14862
14863 int
14864 ToUpper(c)
14865      int c;
14866 {
14867     return islower(c) ? toupper(c) : c;
14868 }
14869 #endif /* !_amigados    */
14870
14871 char *
14872 StrSave(s)
14873      char *s;
14874 {
14875   char *ret;
14876
14877   if ((ret = (char *) malloc(strlen(s) + 1)))
14878     {
14879       safeStrCpy(ret, s, strlen(s)+1);
14880     }
14881   return ret;
14882 }
14883
14884 char *
14885 StrSavePtr(s, savePtr)
14886      char *s, **savePtr;
14887 {
14888     if (*savePtr) {
14889         free(*savePtr);
14890     }
14891     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14892       safeStrCpy(*savePtr, s, strlen(s)+1);
14893     }
14894     return(*savePtr);
14895 }
14896
14897 char *
14898 PGNDate()
14899 {
14900     time_t clock;
14901     struct tm *tm;
14902     char buf[MSG_SIZ];
14903
14904     clock = time((time_t *)NULL);
14905     tm = localtime(&clock);
14906     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14907             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14908     return StrSave(buf);
14909 }
14910
14911
14912 char *
14913 PositionToFEN(move, overrideCastling)
14914      int move;
14915      char *overrideCastling;
14916 {
14917     int i, j, fromX, fromY, toX, toY;
14918     int whiteToPlay;
14919     char buf[128];
14920     char *p, *q;
14921     int emptycount;
14922     ChessSquare piece;
14923
14924     whiteToPlay = (gameMode == EditPosition) ?
14925       !blackPlaysFirst : (move % 2 == 0);
14926     p = buf;
14927
14928     /* Piece placement data */
14929     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14930         emptycount = 0;
14931         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14932             if (boards[move][i][j] == EmptySquare) {
14933                 emptycount++;
14934             } else { ChessSquare piece = boards[move][i][j];
14935                 if (emptycount > 0) {
14936                     if(emptycount<10) /* [HGM] can be >= 10 */
14937                         *p++ = '0' + emptycount;
14938                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14939                     emptycount = 0;
14940                 }
14941                 if(PieceToChar(piece) == '+') {
14942                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14943                     *p++ = '+';
14944                     piece = (ChessSquare)(DEMOTED piece);
14945                 }
14946                 *p++ = PieceToChar(piece);
14947                 if(p[-1] == '~') {
14948                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14949                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14950                     *p++ = '~';
14951                 }
14952             }
14953         }
14954         if (emptycount > 0) {
14955             if(emptycount<10) /* [HGM] can be >= 10 */
14956                 *p++ = '0' + emptycount;
14957             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14958             emptycount = 0;
14959         }
14960         *p++ = '/';
14961     }
14962     *(p - 1) = ' ';
14963
14964     /* [HGM] print Crazyhouse or Shogi holdings */
14965     if( gameInfo.holdingsWidth ) {
14966         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14967         q = p;
14968         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14969             piece = boards[move][i][BOARD_WIDTH-1];
14970             if( piece != EmptySquare )
14971               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14972                   *p++ = PieceToChar(piece);
14973         }
14974         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14975             piece = boards[move][BOARD_HEIGHT-i-1][0];
14976             if( piece != EmptySquare )
14977               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14978                   *p++ = PieceToChar(piece);
14979         }
14980
14981         if( q == p ) *p++ = '-';
14982         *p++ = ']';
14983         *p++ = ' ';
14984     }
14985
14986     /* Active color */
14987     *p++ = whiteToPlay ? 'w' : 'b';
14988     *p++ = ' ';
14989
14990   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14991     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14992   } else {
14993   if(nrCastlingRights) {
14994      q = p;
14995      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14996        /* [HGM] write directly from rights */
14997            if(boards[move][CASTLING][2] != NoRights &&
14998               boards[move][CASTLING][0] != NoRights   )
14999                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15000            if(boards[move][CASTLING][2] != NoRights &&
15001               boards[move][CASTLING][1] != NoRights   )
15002                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15003            if(boards[move][CASTLING][5] != NoRights &&
15004               boards[move][CASTLING][3] != NoRights   )
15005                 *p++ = boards[move][CASTLING][3] + AAA;
15006            if(boards[move][CASTLING][5] != NoRights &&
15007               boards[move][CASTLING][4] != NoRights   )
15008                 *p++ = boards[move][CASTLING][4] + AAA;
15009      } else {
15010
15011         /* [HGM] write true castling rights */
15012         if( nrCastlingRights == 6 ) {
15013             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15014                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15015             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15016                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15017             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15018                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15019             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15020                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15021         }
15022      }
15023      if (q == p) *p++ = '-'; /* No castling rights */
15024      *p++ = ' ';
15025   }
15026
15027   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15028      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15029     /* En passant target square */
15030     if (move > backwardMostMove) {
15031         fromX = moveList[move - 1][0] - AAA;
15032         fromY = moveList[move - 1][1] - ONE;
15033         toX = moveList[move - 1][2] - AAA;
15034         toY = moveList[move - 1][3] - ONE;
15035         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15036             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15037             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15038             fromX == toX) {
15039             /* 2-square pawn move just happened */
15040             *p++ = toX + AAA;
15041             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15042         } else {
15043             *p++ = '-';
15044         }
15045     } else if(move == backwardMostMove) {
15046         // [HGM] perhaps we should always do it like this, and forget the above?
15047         if((signed char)boards[move][EP_STATUS] >= 0) {
15048             *p++ = boards[move][EP_STATUS] + AAA;
15049             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15050         } else {
15051             *p++ = '-';
15052         }
15053     } else {
15054         *p++ = '-';
15055     }
15056     *p++ = ' ';
15057   }
15058   }
15059
15060     /* [HGM] find reversible plies */
15061     {   int i = 0, j=move;
15062
15063         if (appData.debugMode) { int k;
15064             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15065             for(k=backwardMostMove; k<=forwardMostMove; k++)
15066                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15067
15068         }
15069
15070         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15071         if( j == backwardMostMove ) i += initialRulePlies;
15072         sprintf(p, "%d ", i);
15073         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15074     }
15075     /* Fullmove number */
15076     sprintf(p, "%d", (move / 2) + 1);
15077
15078     return StrSave(buf);
15079 }
15080
15081 Boolean
15082 ParseFEN(board, blackPlaysFirst, fen)
15083     Board board;
15084      int *blackPlaysFirst;
15085      char *fen;
15086 {
15087     int i, j;
15088     char *p, c;
15089     int emptycount;
15090     ChessSquare piece;
15091
15092     p = fen;
15093
15094     /* [HGM] by default clear Crazyhouse holdings, if present */
15095     if(gameInfo.holdingsWidth) {
15096        for(i=0; i<BOARD_HEIGHT; i++) {
15097            board[i][0]             = EmptySquare; /* black holdings */
15098            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15099            board[i][1]             = (ChessSquare) 0; /* black counts */
15100            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15101        }
15102     }
15103
15104     /* Piece placement data */
15105     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15106         j = 0;
15107         for (;;) {
15108             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15109                 if (*p == '/') p++;
15110                 emptycount = gameInfo.boardWidth - j;
15111                 while (emptycount--)
15112                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15113                 break;
15114 #if(BOARD_FILES >= 10)
15115             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15116                 p++; emptycount=10;
15117                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15118                 while (emptycount--)
15119                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15120 #endif
15121             } else if (isdigit(*p)) {
15122                 emptycount = *p++ - '0';
15123                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15124                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15125                 while (emptycount--)
15126                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15127             } else if (*p == '+' || isalpha(*p)) {
15128                 if (j >= gameInfo.boardWidth) return FALSE;
15129                 if(*p=='+') {
15130                     piece = CharToPiece(*++p);
15131                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15132                     piece = (ChessSquare) (PROMOTED piece ); p++;
15133                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15134                 } else piece = CharToPiece(*p++);
15135
15136                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15137                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15138                     piece = (ChessSquare) (PROMOTED piece);
15139                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15140                     p++;
15141                 }
15142                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15143             } else {
15144                 return FALSE;
15145             }
15146         }
15147     }
15148     while (*p == '/' || *p == ' ') p++;
15149
15150     /* [HGM] look for Crazyhouse holdings here */
15151     while(*p==' ') p++;
15152     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15153         if(*p == '[') p++;
15154         if(*p == '-' ) p++; /* empty holdings */ else {
15155             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15156             /* if we would allow FEN reading to set board size, we would   */
15157             /* have to add holdings and shift the board read so far here   */
15158             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15159                 p++;
15160                 if((int) piece >= (int) BlackPawn ) {
15161                     i = (int)piece - (int)BlackPawn;
15162                     i = PieceToNumber((ChessSquare)i);
15163                     if( i >= gameInfo.holdingsSize ) return FALSE;
15164                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15165                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15166                 } else {
15167                     i = (int)piece - (int)WhitePawn;
15168                     i = PieceToNumber((ChessSquare)i);
15169                     if( i >= gameInfo.holdingsSize ) return FALSE;
15170                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15171                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15172                 }
15173             }
15174         }
15175         if(*p == ']') p++;
15176     }
15177
15178     while(*p == ' ') p++;
15179
15180     /* Active color */
15181     c = *p++;
15182     if(appData.colorNickNames) {
15183       if( c == appData.colorNickNames[0] ) c = 'w'; else
15184       if( c == appData.colorNickNames[1] ) c = 'b';
15185     }
15186     switch (c) {
15187       case 'w':
15188         *blackPlaysFirst = FALSE;
15189         break;
15190       case 'b':
15191         *blackPlaysFirst = TRUE;
15192         break;
15193       default:
15194         return FALSE;
15195     }
15196
15197     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15198     /* return the extra info in global variiables             */
15199
15200     /* set defaults in case FEN is incomplete */
15201     board[EP_STATUS] = EP_UNKNOWN;
15202     for(i=0; i<nrCastlingRights; i++ ) {
15203         board[CASTLING][i] =
15204             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15205     }   /* assume possible unless obviously impossible */
15206     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15207     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15208     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15209                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15210     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15211     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15212     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15213                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15214     FENrulePlies = 0;
15215
15216     while(*p==' ') p++;
15217     if(nrCastlingRights) {
15218       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15219           /* castling indicator present, so default becomes no castlings */
15220           for(i=0; i<nrCastlingRights; i++ ) {
15221                  board[CASTLING][i] = NoRights;
15222           }
15223       }
15224       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15225              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15226              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15227              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15228         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15229
15230         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15231             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15232             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15233         }
15234         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15235             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15236         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15237                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15238         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15239                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15240         switch(c) {
15241           case'K':
15242               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15243               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15244               board[CASTLING][2] = whiteKingFile;
15245               break;
15246           case'Q':
15247               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15248               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15249               board[CASTLING][2] = whiteKingFile;
15250               break;
15251           case'k':
15252               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15253               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15254               board[CASTLING][5] = blackKingFile;
15255               break;
15256           case'q':
15257               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15258               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15259               board[CASTLING][5] = blackKingFile;
15260           case '-':
15261               break;
15262           default: /* FRC castlings */
15263               if(c >= 'a') { /* black rights */
15264                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15265                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15266                   if(i == BOARD_RGHT) break;
15267                   board[CASTLING][5] = i;
15268                   c -= AAA;
15269                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15270                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15271                   if(c > i)
15272                       board[CASTLING][3] = c;
15273                   else
15274                       board[CASTLING][4] = c;
15275               } else { /* white rights */
15276                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15277                     if(board[0][i] == WhiteKing) break;
15278                   if(i == BOARD_RGHT) break;
15279                   board[CASTLING][2] = i;
15280                   c -= AAA - 'a' + 'A';
15281                   if(board[0][c] >= WhiteKing) break;
15282                   if(c > i)
15283                       board[CASTLING][0] = c;
15284                   else
15285                       board[CASTLING][1] = c;
15286               }
15287         }
15288       }
15289       for(i=0; i<nrCastlingRights; i++)
15290         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15291     if (appData.debugMode) {
15292         fprintf(debugFP, "FEN castling rights:");
15293         for(i=0; i<nrCastlingRights; i++)
15294         fprintf(debugFP, " %d", board[CASTLING][i]);
15295         fprintf(debugFP, "\n");
15296     }
15297
15298       while(*p==' ') p++;
15299     }
15300
15301     /* read e.p. field in games that know e.p. capture */
15302     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15303        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15304       if(*p=='-') {
15305         p++; board[EP_STATUS] = EP_NONE;
15306       } else {
15307          char c = *p++ - AAA;
15308
15309          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15310          if(*p >= '0' && *p <='9') p++;
15311          board[EP_STATUS] = c;
15312       }
15313     }
15314
15315
15316     if(sscanf(p, "%d", &i) == 1) {
15317         FENrulePlies = i; /* 50-move ply counter */
15318         /* (The move number is still ignored)    */
15319     }
15320
15321     return TRUE;
15322 }
15323
15324 void
15325 EditPositionPasteFEN(char *fen)
15326 {
15327   if (fen != NULL) {
15328     Board initial_position;
15329
15330     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15331       DisplayError(_("Bad FEN position in clipboard"), 0);
15332       return ;
15333     } else {
15334       int savedBlackPlaysFirst = blackPlaysFirst;
15335       EditPositionEvent();
15336       blackPlaysFirst = savedBlackPlaysFirst;
15337       CopyBoard(boards[0], initial_position);
15338       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15339       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15340       DisplayBothClocks();
15341       DrawPosition(FALSE, boards[currentMove]);
15342     }
15343   }
15344 }
15345
15346 static char cseq[12] = "\\   ";
15347
15348 Boolean set_cont_sequence(char *new_seq)
15349 {
15350     int len;
15351     Boolean ret;
15352
15353     // handle bad attempts to set the sequence
15354         if (!new_seq)
15355                 return 0; // acceptable error - no debug
15356
15357     len = strlen(new_seq);
15358     ret = (len > 0) && (len < sizeof(cseq));
15359     if (ret)
15360       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15361     else if (appData.debugMode)
15362       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15363     return ret;
15364 }
15365
15366 /*
15367     reformat a source message so words don't cross the width boundary.  internal
15368     newlines are not removed.  returns the wrapped size (no null character unless
15369     included in source message).  If dest is NULL, only calculate the size required
15370     for the dest buffer.  lp argument indicats line position upon entry, and it's
15371     passed back upon exit.
15372 */
15373 int wrap(char *dest, char *src, int count, int width, int *lp)
15374 {
15375     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15376
15377     cseq_len = strlen(cseq);
15378     old_line = line = *lp;
15379     ansi = len = clen = 0;
15380
15381     for (i=0; i < count; i++)
15382     {
15383         if (src[i] == '\033')
15384             ansi = 1;
15385
15386         // if we hit the width, back up
15387         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15388         {
15389             // store i & len in case the word is too long
15390             old_i = i, old_len = len;
15391
15392             // find the end of the last word
15393             while (i && src[i] != ' ' && src[i] != '\n')
15394             {
15395                 i--;
15396                 len--;
15397             }
15398
15399             // word too long?  restore i & len before splitting it
15400             if ((old_i-i+clen) >= width)
15401             {
15402                 i = old_i;
15403                 len = old_len;
15404             }
15405
15406             // extra space?
15407             if (i && src[i-1] == ' ')
15408                 len--;
15409
15410             if (src[i] != ' ' && src[i] != '\n')
15411             {
15412                 i--;
15413                 if (len)
15414                     len--;
15415             }
15416
15417             // now append the newline and continuation sequence
15418             if (dest)
15419                 dest[len] = '\n';
15420             len++;
15421             if (dest)
15422                 strncpy(dest+len, cseq, cseq_len);
15423             len += cseq_len;
15424             line = cseq_len;
15425             clen = cseq_len;
15426             continue;
15427         }
15428
15429         if (dest)
15430             dest[len] = src[i];
15431         len++;
15432         if (!ansi)
15433             line++;
15434         if (src[i] == '\n')
15435             line = 0;
15436         if (src[i] == 'm')
15437             ansi = 0;
15438     }
15439     if (dest && appData.debugMode)
15440     {
15441         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15442             count, width, line, len, *lp);
15443         show_bytes(debugFP, src, count);
15444         fprintf(debugFP, "\ndest: ");
15445         show_bytes(debugFP, dest, len);
15446         fprintf(debugFP, "\n");
15447     }
15448     *lp = dest ? line : old_line;
15449
15450     return len;
15451 }
15452
15453 // [HGM] vari: routines for shelving variations
15454
15455 void
15456 PushTail(int firstMove, int lastMove)
15457 {
15458         int i, j, nrMoves = lastMove - firstMove;
15459
15460         if(appData.icsActive) { // only in local mode
15461                 forwardMostMove = currentMove; // mimic old ICS behavior
15462                 return;
15463         }
15464         if(storedGames >= MAX_VARIATIONS-1) return;
15465
15466         // push current tail of game on stack
15467         savedResult[storedGames] = gameInfo.result;
15468         savedDetails[storedGames] = gameInfo.resultDetails;
15469         gameInfo.resultDetails = NULL;
15470         savedFirst[storedGames] = firstMove;
15471         savedLast [storedGames] = lastMove;
15472         savedFramePtr[storedGames] = framePtr;
15473         framePtr -= nrMoves; // reserve space for the boards
15474         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15475             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15476             for(j=0; j<MOVE_LEN; j++)
15477                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15478             for(j=0; j<2*MOVE_LEN; j++)
15479                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15480             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15481             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15482             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15483             pvInfoList[firstMove+i-1].depth = 0;
15484             commentList[framePtr+i] = commentList[firstMove+i];
15485             commentList[firstMove+i] = NULL;
15486         }
15487
15488         storedGames++;
15489         forwardMostMove = firstMove; // truncate game so we can start variation
15490         if(storedGames == 1) GreyRevert(FALSE);
15491 }
15492
15493 Boolean
15494 PopTail(Boolean annotate)
15495 {
15496         int i, j, nrMoves;
15497         char buf[8000], moveBuf[20];
15498
15499         if(appData.icsActive) return FALSE; // only in local mode
15500         if(!storedGames) return FALSE; // sanity
15501         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15502
15503         storedGames--;
15504         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15505         nrMoves = savedLast[storedGames] - currentMove;
15506         if(annotate) {
15507                 int cnt = 10;
15508                 if(!WhiteOnMove(currentMove))
15509                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15510                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15511                 for(i=currentMove; i<forwardMostMove; i++) {
15512                         if(WhiteOnMove(i))
15513                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15514                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15515                         strcat(buf, moveBuf);
15516                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15517                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15518                 }
15519                 strcat(buf, ")");
15520         }
15521         for(i=1; i<=nrMoves; i++) { // copy last variation back
15522             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15523             for(j=0; j<MOVE_LEN; j++)
15524                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15525             for(j=0; j<2*MOVE_LEN; j++)
15526                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15527             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15528             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15529             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15530             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15531             commentList[currentMove+i] = commentList[framePtr+i];
15532             commentList[framePtr+i] = NULL;
15533         }
15534         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15535         framePtr = savedFramePtr[storedGames];
15536         gameInfo.result = savedResult[storedGames];
15537         if(gameInfo.resultDetails != NULL) {
15538             free(gameInfo.resultDetails);
15539       }
15540         gameInfo.resultDetails = savedDetails[storedGames];
15541         forwardMostMove = currentMove + nrMoves;
15542         if(storedGames == 0) GreyRevert(TRUE);
15543         return TRUE;
15544 }
15545
15546 void
15547 CleanupTail()
15548 {       // remove all shelved variations
15549         int i;
15550         for(i=0; i<storedGames; i++) {
15551             if(savedDetails[i])
15552                 free(savedDetails[i]);
15553             savedDetails[i] = NULL;
15554         }
15555         for(i=framePtr; i<MAX_MOVES; i++) {
15556                 if(commentList[i]) free(commentList[i]);
15557                 commentList[i] = NULL;
15558         }
15559         framePtr = MAX_MOVES-1;
15560         storedGames = 0;
15561 }
15562
15563 void
15564 LoadVariation(int index, char *text)
15565 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15566         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15567         int level = 0, move;
15568
15569         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15570         // first find outermost bracketing variation
15571         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15572             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15573                 if(*p == '{') wait = '}'; else
15574                 if(*p == '[') wait = ']'; else
15575                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15576                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15577             }
15578             if(*p == wait) wait = NULLCHAR; // closing ]} found
15579             p++;
15580         }
15581         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15582         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15583         end[1] = NULLCHAR; // clip off comment beyond variation
15584         ToNrEvent(currentMove-1);
15585         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15586         // kludge: use ParsePV() to append variation to game
15587         move = currentMove;
15588         ParsePV(start, TRUE);
15589         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15590         ClearPremoveHighlights();
15591         CommentPopDown();
15592         ToNrEvent(currentMove+1);
15593 }
15594