Fix mouse-driver buglet
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 { // [HGM] made safe
315   int i;
316   assert( dst != NULL );
317   assert( src != NULL );
318   assert( count > 0 );
319
320   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
321   if(  i == count-1 && dst[i] != NULLCHAR)
322     {
323       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324       if(appData.debugMode)
325       printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
326     }
327
328   return dst;
329 }
330
331 /* Some compiler can't cast u64 to double
332  * This function do the job for us:
333
334  * We use the highest bit for cast, this only
335  * works if the highest bit is not
336  * in use (This should not happen)
337  *
338  * We used this for all compiler
339  */
340 double
341 u64ToDouble(u64 value)
342 {
343   double r;
344   u64 tmp = value & u64Const(0x7fffffffffffffff);
345   r = (double)(s64)tmp;
346   if (value & u64Const(0x8000000000000000))
347        r +=  9.2233720368547758080e18; /* 2^63 */
348  return r;
349 }
350
351 /* Fake up flags for now, as we aren't keeping track of castling
352    availability yet. [HGM] Change of logic: the flag now only
353    indicates the type of castlings allowed by the rule of the game.
354    The actual rights themselves are maintained in the array
355    castlingRights, as part of the game history, and are not probed
356    by this function.
357  */
358 int
359 PosFlags(index)
360 {
361   int flags = F_ALL_CASTLE_OK;
362   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
363   switch (gameInfo.variant) {
364   case VariantSuicide:
365     flags &= ~F_ALL_CASTLE_OK;
366   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
367     flags |= F_IGNORE_CHECK;
368   case VariantLosers:
369     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
370     break;
371   case VariantAtomic:
372     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
373     break;
374   case VariantKriegspiel:
375     flags |= F_KRIEGSPIEL_CAPTURE;
376     break;
377   case VariantCapaRandom:
378   case VariantFischeRandom:
379     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
380   case VariantNoCastle:
381   case VariantShatranj:
382   case VariantCourier:
383   case VariantMakruk:
384     flags &= ~F_ALL_CASTLE_OK;
385     break;
386   default:
387     break;
388   }
389   return flags;
390 }
391
392 FILE *gameFileFP, *debugFP;
393
394 /*
395     [AS] Note: sometimes, the sscanf() function is used to parse the input
396     into a fixed-size buffer. Because of this, we must be prepared to
397     receive strings as long as the size of the input buffer, which is currently
398     set to 4K for Windows and 8K for the rest.
399     So, we must either allocate sufficiently large buffers here, or
400     reduce the size of the input buffer in the input reading part.
401 */
402
403 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
404 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
405 char thinkOutput1[MSG_SIZ*10];
406
407 ChessProgramState first, second;
408
409 /* premove variables */
410 int premoveToX = 0;
411 int premoveToY = 0;
412 int premoveFromX = 0;
413 int premoveFromY = 0;
414 int premovePromoChar = 0;
415 int gotPremove = 0;
416 Boolean alarmSounded;
417 /* end premove variables */
418
419 char *ics_prefix = "$";
420 int ics_type = ICS_GENERIC;
421
422 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
423 int pauseExamForwardMostMove = 0;
424 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
425 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
426 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
427 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
428 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
429 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
430 int whiteFlag = FALSE, blackFlag = FALSE;
431 int userOfferedDraw = FALSE;
432 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
433 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
434 int cmailMoveType[CMAIL_MAX_GAMES];
435 long ics_clock_paused = 0;
436 ProcRef icsPR = NoProc, cmailPR = NoProc;
437 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
438 GameMode gameMode = BeginningOfGame;
439 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
440 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
441 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
442 int hiddenThinkOutputState = 0; /* [AS] */
443 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
444 int adjudicateLossPlies = 6;
445 char white_holding[64], black_holding[64];
446 TimeMark lastNodeCountTime;
447 long lastNodeCount=0;
448 int shiftKey; // [HGM] set by mouse handler
449
450 int have_sent_ICS_logon = 0;
451 int sending_ICS_login    = 0;
452 int sending_ICS_password = 0;
453
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
460 int matchGame = 0;
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
464
465 /* animateTraining preserves the state of appData.animate
466  * when Training mode is activated. This allows the
467  * response to be animated when appData.animate == TRUE and
468  * appData.animateDragging == TRUE.
469  */
470 Boolean animateTraining;
471
472 GameInfo gameInfo;
473
474 AppData appData;
475
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char  initialRights[BOARD_FILES];
480 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int   initialRulePlies, FENrulePlies;
482 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
483 int loadFlag = 0;
484 int shuffleOpenings;
485 int mute; // mute all sounds
486
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int storedGames = 0;
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
496
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
500
501 ChessSquare  FIDEArray[2][BOARD_FILES] = {
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505         BlackKing, BlackBishop, BlackKnight, BlackRook }
506 };
507
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackKing, BlackKnight, BlackRook }
513 };
514
515 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518     { BlackRook, BlackMan, BlackBishop, BlackQueen,
519         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
520 };
521
522 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
526         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
527 };
528
529 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
530     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
531         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
533         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
534 };
535
536 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
538         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
539     { BlackRook, BlackKnight, BlackMan, BlackFerz,
540         BlackKing, BlackMan, BlackKnight, BlackRook }
541 };
542
543
544 #if (BOARD_FILES>=10)
545 ChessSquare ShogiArray[2][BOARD_FILES] = {
546     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
547         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
548     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
549         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
550 };
551
552 ChessSquare XiangqiArray[2][BOARD_FILES] = {
553     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
554         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
556         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
557 };
558
559 ChessSquare CapablancaArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
561         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
563         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
564 };
565
566 ChessSquare GreatArray[2][BOARD_FILES] = {
567     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
568         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
569     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
570         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
571 };
572
573 ChessSquare JanusArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
575         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
576     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
577         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
578 };
579
580 #ifdef GOTHIC
581 ChessSquare GothicArray[2][BOARD_FILES] = {
582     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
583         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
585         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
586 };
587 #else // !GOTHIC
588 #define GothicArray CapablancaArray
589 #endif // !GOTHIC
590
591 #ifdef FALCON
592 ChessSquare FalconArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
594         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
596         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
597 };
598 #else // !FALCON
599 #define FalconArray CapablancaArray
600 #endif // !FALCON
601
602 #else // !(BOARD_FILES>=10)
603 #define XiangqiPosition FIDEArray
604 #define CapablancaArray FIDEArray
605 #define GothicArray FIDEArray
606 #define GreatArray FIDEArray
607 #endif // !(BOARD_FILES>=10)
608
609 #if (BOARD_FILES>=12)
610 ChessSquare CourierArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
612         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
614         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
615 };
616 #else // !(BOARD_FILES>=12)
617 #define CourierArray CapablancaArray
618 #endif // !(BOARD_FILES>=12)
619
620
621 Board initialPosition;
622
623
624 /* Convert str to a rating. Checks for special cases of "----",
625
626    "++++", etc. Also strips ()'s */
627 int
628 string_to_rating(str)
629   char *str;
630 {
631   while(*str && !isdigit(*str)) ++str;
632   if (!*str)
633     return 0;   /* One of the special "no rating" cases */
634   else
635     return atoi(str);
636 }
637
638 void
639 ClearProgramStats()
640 {
641     /* Init programStats */
642     programStats.movelist[0] = 0;
643     programStats.depth = 0;
644     programStats.nr_moves = 0;
645     programStats.moves_left = 0;
646     programStats.nodes = 0;
647     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
648     programStats.score = 0;
649     programStats.got_only_move = 0;
650     programStats.got_fail = 0;
651     programStats.line_is_book = 0;
652 }
653
654 void
655 InitBackEnd1()
656 {
657     int matched, min, sec;
658
659     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
660     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
661
662     GetTimeMark(&programStartTime);
663     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
664
665     ClearProgramStats();
666     programStats.ok_to_send = 1;
667     programStats.seen_stat = 0;
668
669     /*
670      * Initialize game list
671      */
672     ListNew(&gameList);
673
674
675     /*
676      * Internet chess server status
677      */
678     if (appData.icsActive) {
679         appData.matchMode = FALSE;
680         appData.matchGames = 0;
681 #if ZIPPY
682         appData.noChessProgram = !appData.zippyPlay;
683 #else
684         appData.zippyPlay = FALSE;
685         appData.zippyTalk = FALSE;
686         appData.noChessProgram = TRUE;
687 #endif
688         if (*appData.icsHelper != NULLCHAR) {
689             appData.useTelnet = TRUE;
690             appData.telnetProgram = appData.icsHelper;
691         }
692     } else {
693         appData.zippyTalk = appData.zippyPlay = FALSE;
694     }
695
696     /* [AS] Initialize pv info list [HGM] and game state */
697     {
698         int i, j;
699
700         for( i=0; i<=framePtr; i++ ) {
701             pvInfoList[i].depth = -1;
702             boards[i][EP_STATUS] = EP_NONE;
703             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
704         }
705     }
706
707     /*
708      * Parse timeControl resource
709      */
710     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
711                           appData.movesPerSession)) {
712         char buf[MSG_SIZ];
713         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
714         DisplayFatalError(buf, 0, 2);
715     }
716
717     /*
718      * Parse searchTime resource
719      */
720     if (*appData.searchTime != NULLCHAR) {
721         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
722         if (matched == 1) {
723             searchTime = min * 60;
724         } else if (matched == 2) {
725             searchTime = min * 60 + sec;
726         } else {
727             char buf[MSG_SIZ];
728             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
729             DisplayFatalError(buf, 0, 2);
730         }
731     }
732
733     /* [AS] Adjudication threshold */
734     adjudicateLossThreshold = appData.adjudicateLossThreshold;
735
736     first.which = _("first");
737     second.which = _("second");
738     first.maybeThinking = second.maybeThinking = FALSE;
739     first.pr = second.pr = NoProc;
740     first.isr = second.isr = NULL;
741     first.sendTime = second.sendTime = 2;
742     first.sendDrawOffers = 1;
743     if (appData.firstPlaysBlack) {
744         first.twoMachinesColor = "black\n";
745         second.twoMachinesColor = "white\n";
746     } else {
747         first.twoMachinesColor = "white\n";
748         second.twoMachinesColor = "black\n";
749     }
750     first.program = appData.firstChessProgram;
751     second.program = appData.secondChessProgram;
752     first.host = appData.firstHost;
753     second.host = appData.secondHost;
754     first.dir = appData.firstDirectory;
755     second.dir = appData.secondDirectory;
756     first.other = &second;
757     second.other = &first;
758     first.initString = appData.initString;
759     second.initString = appData.secondInitString;
760     first.computerString = appData.firstComputerString;
761     second.computerString = appData.secondComputerString;
762     first.useSigint = second.useSigint = TRUE;
763     first.useSigterm = second.useSigterm = TRUE;
764     first.reuse = appData.reuseFirst;
765     second.reuse = appData.reuseSecond;
766     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
767     second.nps = appData.secondNPS;
768     first.useSetboard = second.useSetboard = FALSE;
769     first.useSAN = second.useSAN = FALSE;
770     first.usePing = second.usePing = FALSE;
771     first.lastPing = second.lastPing = 0;
772     first.lastPong = second.lastPong = 0;
773     first.usePlayother = second.usePlayother = FALSE;
774     first.useColors = second.useColors = TRUE;
775     first.useUsermove = second.useUsermove = FALSE;
776     first.sendICS = second.sendICS = FALSE;
777     first.sendName = second.sendName = appData.icsActive;
778     first.sdKludge = second.sdKludge = FALSE;
779     first.stKludge = second.stKludge = FALSE;
780     TidyProgramName(first.program, first.host, first.tidy);
781     TidyProgramName(second.program, second.host, second.tidy);
782     first.matchWins = second.matchWins = 0;
783     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
784     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
785     first.analysisSupport = second.analysisSupport = 2; /* detect */
786     first.analyzing = second.analyzing = FALSE;
787     first.initDone = second.initDone = FALSE;
788
789     /* New features added by Tord: */
790     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
791     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
792     /* End of new features added by Tord. */
793     first.fenOverride  = appData.fenOverride1;
794     second.fenOverride = appData.fenOverride2;
795
796     /* [HGM] time odds: set factor for each machine */
797     first.timeOdds  = appData.firstTimeOdds;
798     second.timeOdds = appData.secondTimeOdds;
799     { float norm = 1;
800         if(appData.timeOddsMode) {
801             norm = first.timeOdds;
802             if(norm > second.timeOdds) norm = second.timeOdds;
803         }
804         first.timeOdds /= norm;
805         second.timeOdds /= norm;
806     }
807
808     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
809     first.accumulateTC = appData.firstAccumulateTC;
810     second.accumulateTC = appData.secondAccumulateTC;
811     first.maxNrOfSessions = second.maxNrOfSessions = 1;
812
813     /* [HGM] debug */
814     first.debug = second.debug = FALSE;
815     first.supportsNPS = second.supportsNPS = UNKNOWN;
816
817     /* [HGM] options */
818     first.optionSettings  = appData.firstOptions;
819     second.optionSettings = appData.secondOptions;
820
821     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
822     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
823     first.isUCI = appData.firstIsUCI; /* [AS] */
824     second.isUCI = appData.secondIsUCI; /* [AS] */
825     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
826     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
827
828     if (appData.firstProtocolVersion > PROTOVER
829         || appData.firstProtocolVersion < 1)
830       {
831         char buf[MSG_SIZ];
832         int len;
833
834         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
835                        appData.firstProtocolVersion);
836         if( (len > MSG_SIZ) && appData.debugMode )
837           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
838
839         DisplayFatalError(buf, 0, 2);
840       }
841     else
842       {
843         first.protocolVersion = appData.firstProtocolVersion;
844       }
845
846     if (appData.secondProtocolVersion > PROTOVER
847         || appData.secondProtocolVersion < 1)
848       {
849         char buf[MSG_SIZ];
850         int len;
851
852         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
853                        appData.secondProtocolVersion);
854         if( (len > MSG_SIZ) && appData.debugMode )
855           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
856
857         DisplayFatalError(buf, 0, 2);
858       }
859     else
860       {
861         second.protocolVersion = appData.secondProtocolVersion;
862       }
863
864     if (appData.icsActive) {
865         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
866 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
867     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
868         appData.clockMode = FALSE;
869         first.sendTime = second.sendTime = 0;
870     }
871
872 #if ZIPPY
873     /* Override some settings from environment variables, for backward
874        compatibility.  Unfortunately it's not feasible to have the env
875        vars just set defaults, at least in xboard.  Ugh.
876     */
877     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
878       ZippyInit();
879     }
880 #endif
881
882     if (appData.noChessProgram) {
883         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
884         sprintf(programVersion, "%s", PACKAGE_STRING);
885     } else {
886       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
887       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
888       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
889     }
890
891     if (!appData.icsActive) {
892       char buf[MSG_SIZ];
893       int len;
894
895       /* Check for variants that are supported only in ICS mode,
896          or not at all.  Some that are accepted here nevertheless
897          have bugs; see comments below.
898       */
899       VariantClass variant = StringToVariant(appData.variant);
900       switch (variant) {
901       case VariantBughouse:     /* need four players and two boards */
902       case VariantKriegspiel:   /* need to hide pieces and move details */
903         /* case VariantFischeRandom: (Fabien: moved below) */
904         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
905         if( (len > MSG_SIZ) && appData.debugMode )
906           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
907
908         DisplayFatalError(buf, 0, 2);
909         return;
910
911       case VariantUnknown:
912       case VariantLoadable:
913       case Variant29:
914       case Variant30:
915       case Variant31:
916       case Variant32:
917       case Variant33:
918       case Variant34:
919       case Variant35:
920       case Variant36:
921       default:
922         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
923         if( (len > MSG_SIZ) && appData.debugMode )
924           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
925
926         DisplayFatalError(buf, 0, 2);
927         return;
928
929       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
930       case VariantFairy:      /* [HGM] TestLegality definitely off! */
931       case VariantGothic:     /* [HGM] should work */
932       case VariantCapablanca: /* [HGM] should work */
933       case VariantCourier:    /* [HGM] initial forced moves not implemented */
934       case VariantShogi:      /* [HGM] could still mate with pawn drop */
935       case VariantKnightmate: /* [HGM] should work */
936       case VariantCylinder:   /* [HGM] untested */
937       case VariantFalcon:     /* [HGM] untested */
938       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
939                                  offboard interposition not understood */
940       case VariantNormal:     /* definitely works! */
941       case VariantWildCastle: /* pieces not automatically shuffled */
942       case VariantNoCastle:   /* pieces not automatically shuffled */
943       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
944       case VariantLosers:     /* should work except for win condition,
945                                  and doesn't know captures are mandatory */
946       case VariantSuicide:    /* should work except for win condition,
947                                  and doesn't know captures are mandatory */
948       case VariantGiveaway:   /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantTwoKings:   /* should work */
951       case VariantAtomic:     /* should work except for win condition */
952       case Variant3Check:     /* should work except for win condition */
953       case VariantShatranj:   /* should work except for all win conditions */
954       case VariantMakruk:     /* should work except for daw countdown */
955       case VariantBerolina:   /* might work if TestLegality is off */
956       case VariantCapaRandom: /* should work */
957       case VariantJanus:      /* should work */
958       case VariantSuper:      /* experimental */
959       case VariantGreat:      /* experimental, requires legality testing to be off */
960         break;
961       }
962     }
963
964     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
965     InitEngineUCI( installDir, &second );
966 }
967
968 int NextIntegerFromString( char ** str, long * value )
969 {
970     int result = -1;
971     char * s = *str;
972
973     while( *s == ' ' || *s == '\t' ) {
974         s++;
975     }
976
977     *value = 0;
978
979     if( *s >= '0' && *s <= '9' ) {
980         while( *s >= '0' && *s <= '9' ) {
981             *value = *value * 10 + (*s - '0');
982             s++;
983         }
984
985         result = 0;
986     }
987
988     *str = s;
989
990     return result;
991 }
992
993 int NextTimeControlFromString( char ** str, long * value )
994 {
995     long temp;
996     int result = NextIntegerFromString( str, &temp );
997
998     if( result == 0 ) {
999         *value = temp * 60; /* Minutes */
1000         if( **str == ':' ) {
1001             (*str)++;
1002             result = NextIntegerFromString( str, &temp );
1003             *value += temp; /* Seconds */
1004         }
1005     }
1006
1007     return result;
1008 }
1009
1010 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1011 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1012     int result = -1, type = 0; long temp, temp2;
1013
1014     if(**str != ':') return -1; // old params remain in force!
1015     (*str)++;
1016     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1017     if( NextIntegerFromString( str, &temp ) ) return -1;
1018     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1019
1020     if(**str != '/') {
1021         /* time only: incremental or sudden-death time control */
1022         if(**str == '+') { /* increment follows; read it */
1023             (*str)++;
1024             if(**str == '!') type = *(*str)++; // Bronstein TC
1025             if(result = NextIntegerFromString( str, &temp2)) return -1;
1026             *inc = temp2 * 1000;
1027             if(**str == '.') { // read fraction of increment
1028                 char *start = ++(*str);
1029                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1030                 temp2 *= 1000;
1031                 while(start++ < *str) temp2 /= 10;
1032                 *inc += temp2;
1033             }
1034         } else *inc = 0;
1035         *moves = 0; *tc = temp * 1000; *incType = type;
1036         return 0;
1037     }
1038
1039     (*str)++; /* classical time control */
1040     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1041
1042     if(result == 0) {
1043         *moves = temp;
1044         *tc    = temp2 * 1000;
1045         *inc   = 0;
1046         *incType = type;
1047     }
1048     return result;
1049 }
1050
1051 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1052 {   /* [HGM] get time to add from the multi-session time-control string */
1053     int incType, moves=1; /* kludge to force reading of first session */
1054     long time, increment;
1055     char *s = tcString;
1056
1057     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1058     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1059     do {
1060         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1061         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1062         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1063         if(movenr == -1) return time;    /* last move before new session     */
1064         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1065         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1066         if(!moves) return increment;     /* current session is incremental   */
1067         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1068     } while(movenr >= -1);               /* try again for next session       */
1069
1070     return 0; // no new time quota on this move
1071 }
1072
1073 int
1074 ParseTimeControl(tc, ti, mps)
1075      char *tc;
1076      float ti;
1077      int mps;
1078 {
1079   long tc1;
1080   long tc2;
1081   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1082   int min, sec=0;
1083
1084   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1085   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1086       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1087   if(ti > 0) {
1088
1089     if(mps)
1090       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1091     else
1092       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1093   } else {
1094     if(mps)
1095       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1096     else
1097       snprintf(buf, MSG_SIZ, ":%s", mytc);
1098   }
1099   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1100
1101   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1102     return FALSE;
1103   }
1104
1105   if( *tc == '/' ) {
1106     /* Parse second time control */
1107     tc++;
1108
1109     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1110       return FALSE;
1111     }
1112
1113     if( tc2 == 0 ) {
1114       return FALSE;
1115     }
1116
1117     timeControl_2 = tc2 * 1000;
1118   }
1119   else {
1120     timeControl_2 = 0;
1121   }
1122
1123   if( tc1 == 0 ) {
1124     return FALSE;
1125   }
1126
1127   timeControl = tc1 * 1000;
1128
1129   if (ti >= 0) {
1130     timeIncrement = ti * 1000;  /* convert to ms */
1131     movesPerSession = 0;
1132   } else {
1133     timeIncrement = 0;
1134     movesPerSession = mps;
1135   }
1136   return TRUE;
1137 }
1138
1139 void
1140 InitBackEnd2()
1141 {
1142     if (appData.debugMode) {
1143         fprintf(debugFP, "%s\n", programVersion);
1144     }
1145
1146     set_cont_sequence(appData.wrapContSeq);
1147     if (appData.matchGames > 0) {
1148         appData.matchMode = TRUE;
1149     } else if (appData.matchMode) {
1150         appData.matchGames = 1;
1151     }
1152     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1153         appData.matchGames = appData.sameColorGames;
1154     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1155         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1156         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1157     }
1158     Reset(TRUE, FALSE);
1159     if (appData.noChessProgram || first.protocolVersion == 1) {
1160       InitBackEnd3();
1161     } else {
1162       /* kludge: allow timeout for initial "feature" commands */
1163       FreezeUI();
1164       DisplayMessage("", _("Starting chess program"));
1165       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1166     }
1167 }
1168
1169 void
1170 InitBackEnd3 P((void))
1171 {
1172     GameMode initialMode;
1173     char buf[MSG_SIZ];
1174     int err, len;
1175
1176     InitChessProgram(&first, startedFromSetupPosition);
1177
1178     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1179         free(programVersion);
1180         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1181         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1182     }
1183
1184     if (appData.icsActive) {
1185 #ifdef WIN32
1186         /* [DM] Make a console window if needed [HGM] merged ifs */
1187         ConsoleCreate();
1188 #endif
1189         err = establish();
1190         if (err != 0)
1191           {
1192             if (*appData.icsCommPort != NULLCHAR)
1193               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1194                              appData.icsCommPort);
1195             else
1196               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1197                         appData.icsHost, appData.icsPort);
1198
1199             if( (len > MSG_SIZ) && appData.debugMode )
1200               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1201
1202             DisplayFatalError(buf, err, 1);
1203             return;
1204         }
1205         SetICSMode();
1206         telnetISR =
1207           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1208         fromUserISR =
1209           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1210         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1211             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1212     } else if (appData.noChessProgram) {
1213         SetNCPMode();
1214     } else {
1215         SetGNUMode();
1216     }
1217
1218     if (*appData.cmailGameName != NULLCHAR) {
1219         SetCmailMode();
1220         OpenLoopback(&cmailPR);
1221         cmailISR =
1222           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1223     }
1224
1225     ThawUI();
1226     DisplayMessage("", "");
1227     if (StrCaseCmp(appData.initialMode, "") == 0) {
1228       initialMode = BeginningOfGame;
1229     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1230       initialMode = TwoMachinesPlay;
1231     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1232       initialMode = AnalyzeFile;
1233     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1234       initialMode = AnalyzeMode;
1235     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1236       initialMode = MachinePlaysWhite;
1237     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1238       initialMode = MachinePlaysBlack;
1239     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1240       initialMode = EditGame;
1241     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1242       initialMode = EditPosition;
1243     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1244       initialMode = Training;
1245     } else {
1246       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1247       if( (len > MSG_SIZ) && appData.debugMode )
1248         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1249
1250       DisplayFatalError(buf, 0, 2);
1251       return;
1252     }
1253
1254     if (appData.matchMode) {
1255         /* Set up machine vs. machine match */
1256         if (appData.noChessProgram) {
1257             DisplayFatalError(_("Can't have a match with no chess programs"),
1258                               0, 2);
1259             return;
1260         }
1261         matchMode = TRUE;
1262         matchGame = 1;
1263         if (*appData.loadGameFile != NULLCHAR) {
1264             int index = appData.loadGameIndex; // [HGM] autoinc
1265             if(index<0) lastIndex = index = 1;
1266             if (!LoadGameFromFile(appData.loadGameFile,
1267                                   index,
1268                                   appData.loadGameFile, FALSE)) {
1269                 DisplayFatalError(_("Bad game file"), 0, 1);
1270                 return;
1271             }
1272         } else if (*appData.loadPositionFile != NULLCHAR) {
1273             int index = appData.loadPositionIndex; // [HGM] autoinc
1274             if(index<0) lastIndex = index = 1;
1275             if (!LoadPositionFromFile(appData.loadPositionFile,
1276                                       index,
1277                                       appData.loadPositionFile)) {
1278                 DisplayFatalError(_("Bad position file"), 0, 1);
1279                 return;
1280             }
1281         }
1282         TwoMachinesEvent();
1283     } else if (*appData.cmailGameName != NULLCHAR) {
1284         /* Set up cmail mode */
1285         ReloadCmailMsgEvent(TRUE);
1286     } else {
1287         /* Set up other modes */
1288         if (initialMode == AnalyzeFile) {
1289           if (*appData.loadGameFile == NULLCHAR) {
1290             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1291             return;
1292           }
1293         }
1294         if (*appData.loadGameFile != NULLCHAR) {
1295             (void) LoadGameFromFile(appData.loadGameFile,
1296                                     appData.loadGameIndex,
1297                                     appData.loadGameFile, TRUE);
1298         } else if (*appData.loadPositionFile != NULLCHAR) {
1299             (void) LoadPositionFromFile(appData.loadPositionFile,
1300                                         appData.loadPositionIndex,
1301                                         appData.loadPositionFile);
1302             /* [HGM] try to make self-starting even after FEN load */
1303             /* to allow automatic setup of fairy variants with wtm */
1304             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1305                 gameMode = BeginningOfGame;
1306                 setboardSpoiledMachineBlack = 1;
1307             }
1308             /* [HGM] loadPos: make that every new game uses the setup */
1309             /* from file as long as we do not switch variant          */
1310             if(!blackPlaysFirst) {
1311                 startedFromPositionFile = TRUE;
1312                 CopyBoard(filePosition, boards[0]);
1313             }
1314         }
1315         if (initialMode == AnalyzeMode) {
1316           if (appData.noChessProgram) {
1317             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1318             return;
1319           }
1320           if (appData.icsActive) {
1321             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1322             return;
1323           }
1324           AnalyzeModeEvent();
1325         } else if (initialMode == AnalyzeFile) {
1326           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1327           ShowThinkingEvent();
1328           AnalyzeFileEvent();
1329           AnalysisPeriodicEvent(1);
1330         } else if (initialMode == MachinePlaysWhite) {
1331           if (appData.noChessProgram) {
1332             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1333                               0, 2);
1334             return;
1335           }
1336           if (appData.icsActive) {
1337             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1338                               0, 2);
1339             return;
1340           }
1341           MachineWhiteEvent();
1342         } else if (initialMode == MachinePlaysBlack) {
1343           if (appData.noChessProgram) {
1344             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1345                               0, 2);
1346             return;
1347           }
1348           if (appData.icsActive) {
1349             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1350                               0, 2);
1351             return;
1352           }
1353           MachineBlackEvent();
1354         } else if (initialMode == TwoMachinesPlay) {
1355           if (appData.noChessProgram) {
1356             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1357                               0, 2);
1358             return;
1359           }
1360           if (appData.icsActive) {
1361             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1362                               0, 2);
1363             return;
1364           }
1365           TwoMachinesEvent();
1366         } else if (initialMode == EditGame) {
1367           EditGameEvent();
1368         } else if (initialMode == EditPosition) {
1369           EditPositionEvent();
1370         } else if (initialMode == Training) {
1371           if (*appData.loadGameFile == NULLCHAR) {
1372             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1373             return;
1374           }
1375           TrainingEvent();
1376         }
1377     }
1378 }
1379
1380 /*
1381  * Establish will establish a contact to a remote host.port.
1382  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1383  *  used to talk to the host.
1384  * Returns 0 if okay, error code if not.
1385  */
1386 int
1387 establish()
1388 {
1389     char buf[MSG_SIZ];
1390
1391     if (*appData.icsCommPort != NULLCHAR) {
1392         /* Talk to the host through a serial comm port */
1393         return OpenCommPort(appData.icsCommPort, &icsPR);
1394
1395     } else if (*appData.gateway != NULLCHAR) {
1396         if (*appData.remoteShell == NULLCHAR) {
1397             /* Use the rcmd protocol to run telnet program on a gateway host */
1398             snprintf(buf, sizeof(buf), "%s %s %s",
1399                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1400             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1401
1402         } else {
1403             /* Use the rsh program to run telnet program on a gateway host */
1404             if (*appData.remoteUser == NULLCHAR) {
1405                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1406                         appData.gateway, appData.telnetProgram,
1407                         appData.icsHost, appData.icsPort);
1408             } else {
1409                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1410                         appData.remoteShell, appData.gateway,
1411                         appData.remoteUser, appData.telnetProgram,
1412                         appData.icsHost, appData.icsPort);
1413             }
1414             return StartChildProcess(buf, "", &icsPR);
1415
1416         }
1417     } else if (appData.useTelnet) {
1418         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1419
1420     } else {
1421         /* TCP socket interface differs somewhat between
1422            Unix and NT; handle details in the front end.
1423            */
1424         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1425     }
1426 }
1427
1428 void EscapeExpand(char *p, char *q)
1429 {       // [HGM] initstring: routine to shape up string arguments
1430         while(*p++ = *q++) if(p[-1] == '\\')
1431             switch(*q++) {
1432                 case 'n': p[-1] = '\n'; break;
1433                 case 'r': p[-1] = '\r'; break;
1434                 case 't': p[-1] = '\t'; break;
1435                 case '\\': p[-1] = '\\'; break;
1436                 case 0: *p = 0; return;
1437                 default: p[-1] = q[-1]; break;
1438             }
1439 }
1440
1441 void
1442 show_bytes(fp, buf, count)
1443      FILE *fp;
1444      char *buf;
1445      int count;
1446 {
1447     while (count--) {
1448         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1449             fprintf(fp, "\\%03o", *buf & 0xff);
1450         } else {
1451             putc(*buf, fp);
1452         }
1453         buf++;
1454     }
1455     fflush(fp);
1456 }
1457
1458 /* Returns an errno value */
1459 int
1460 OutputMaybeTelnet(pr, message, count, outError)
1461      ProcRef pr;
1462      char *message;
1463      int count;
1464      int *outError;
1465 {
1466     char buf[8192], *p, *q, *buflim;
1467     int left, newcount, outcount;
1468
1469     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1470         *appData.gateway != NULLCHAR) {
1471         if (appData.debugMode) {
1472             fprintf(debugFP, ">ICS: ");
1473             show_bytes(debugFP, message, count);
1474             fprintf(debugFP, "\n");
1475         }
1476         return OutputToProcess(pr, message, count, outError);
1477     }
1478
1479     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1480     p = message;
1481     q = buf;
1482     left = count;
1483     newcount = 0;
1484     while (left) {
1485         if (q >= buflim) {
1486             if (appData.debugMode) {
1487                 fprintf(debugFP, ">ICS: ");
1488                 show_bytes(debugFP, buf, newcount);
1489                 fprintf(debugFP, "\n");
1490             }
1491             outcount = OutputToProcess(pr, buf, newcount, outError);
1492             if (outcount < newcount) return -1; /* to be sure */
1493             q = buf;
1494             newcount = 0;
1495         }
1496         if (*p == '\n') {
1497             *q++ = '\r';
1498             newcount++;
1499         } else if (((unsigned char) *p) == TN_IAC) {
1500             *q++ = (char) TN_IAC;
1501             newcount ++;
1502         }
1503         *q++ = *p++;
1504         newcount++;
1505         left--;
1506     }
1507     if (appData.debugMode) {
1508         fprintf(debugFP, ">ICS: ");
1509         show_bytes(debugFP, buf, newcount);
1510         fprintf(debugFP, "\n");
1511     }
1512     outcount = OutputToProcess(pr, buf, newcount, outError);
1513     if (outcount < newcount) return -1; /* to be sure */
1514     return count;
1515 }
1516
1517 void
1518 read_from_player(isr, closure, message, count, error)
1519      InputSourceRef isr;
1520      VOIDSTAR closure;
1521      char *message;
1522      int count;
1523      int error;
1524 {
1525     int outError, outCount;
1526     static int gotEof = 0;
1527
1528     /* Pass data read from player on to ICS */
1529     if (count > 0) {
1530         gotEof = 0;
1531         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1532         if (outCount < count) {
1533             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1534         }
1535     } else if (count < 0) {
1536         RemoveInputSource(isr);
1537         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1538     } else if (gotEof++ > 0) {
1539         RemoveInputSource(isr);
1540         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1541     }
1542 }
1543
1544 void
1545 KeepAlive()
1546 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1547     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1548     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1549     SendToICS("date\n");
1550     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1551 }
1552
1553 /* added routine for printf style output to ics */
1554 void ics_printf(char *format, ...)
1555 {
1556     char buffer[MSG_SIZ];
1557     va_list args;
1558
1559     va_start(args, format);
1560     vsnprintf(buffer, sizeof(buffer), format, args);
1561     buffer[sizeof(buffer)-1] = '\0';
1562     SendToICS(buffer);
1563     va_end(args);
1564 }
1565
1566 void
1567 SendToICS(s)
1568      char *s;
1569 {
1570     int count, outCount, outError;
1571
1572     if (icsPR == NULL) return;
1573
1574     count = strlen(s);
1575     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1576     if (outCount < count) {
1577         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1578     }
1579 }
1580
1581 /* This is used for sending logon scripts to the ICS. Sending
1582    without a delay causes problems when using timestamp on ICC
1583    (at least on my machine). */
1584 void
1585 SendToICSDelayed(s,msdelay)
1586      char *s;
1587      long msdelay;
1588 {
1589     int count, outCount, outError;
1590
1591     if (icsPR == NULL) return;
1592
1593     count = strlen(s);
1594     if (appData.debugMode) {
1595         fprintf(debugFP, ">ICS: ");
1596         show_bytes(debugFP, s, count);
1597         fprintf(debugFP, "\n");
1598     }
1599     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1600                                       msdelay);
1601     if (outCount < count) {
1602         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1603     }
1604 }
1605
1606
1607 /* Remove all highlighting escape sequences in s
1608    Also deletes any suffix starting with '('
1609    */
1610 char *
1611 StripHighlightAndTitle(s)
1612      char *s;
1613 {
1614     static char retbuf[MSG_SIZ];
1615     char *p = retbuf;
1616
1617     while (*s != NULLCHAR) {
1618         while (*s == '\033') {
1619             while (*s != NULLCHAR && !isalpha(*s)) s++;
1620             if (*s != NULLCHAR) s++;
1621         }
1622         while (*s != NULLCHAR && *s != '\033') {
1623             if (*s == '(' || *s == '[') {
1624                 *p = NULLCHAR;
1625                 return retbuf;
1626             }
1627             *p++ = *s++;
1628         }
1629     }
1630     *p = NULLCHAR;
1631     return retbuf;
1632 }
1633
1634 /* Remove all highlighting escape sequences in s */
1635 char *
1636 StripHighlight(s)
1637      char *s;
1638 {
1639     static char retbuf[MSG_SIZ];
1640     char *p = retbuf;
1641
1642     while (*s != NULLCHAR) {
1643         while (*s == '\033') {
1644             while (*s != NULLCHAR && !isalpha(*s)) s++;
1645             if (*s != NULLCHAR) s++;
1646         }
1647         while (*s != NULLCHAR && *s != '\033') {
1648             *p++ = *s++;
1649         }
1650     }
1651     *p = NULLCHAR;
1652     return retbuf;
1653 }
1654
1655 char *variantNames[] = VARIANT_NAMES;
1656 char *
1657 VariantName(v)
1658      VariantClass v;
1659 {
1660     return variantNames[v];
1661 }
1662
1663
1664 /* Identify a variant from the strings the chess servers use or the
1665    PGN Variant tag names we use. */
1666 VariantClass
1667 StringToVariant(e)
1668      char *e;
1669 {
1670     char *p;
1671     int wnum = -1;
1672     VariantClass v = VariantNormal;
1673     int i, found = FALSE;
1674     char buf[MSG_SIZ];
1675     int len;
1676
1677     if (!e) return v;
1678
1679     /* [HGM] skip over optional board-size prefixes */
1680     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1681         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1682         while( *e++ != '_');
1683     }
1684
1685     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1686         v = VariantNormal;
1687         found = TRUE;
1688     } else
1689     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1690       if (StrCaseStr(e, variantNames[i])) {
1691         v = (VariantClass) i;
1692         found = TRUE;
1693         break;
1694       }
1695     }
1696
1697     if (!found) {
1698       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1699           || StrCaseStr(e, "wild/fr")
1700           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1701         v = VariantFischeRandom;
1702       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1703                  (i = 1, p = StrCaseStr(e, "w"))) {
1704         p += i;
1705         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1706         if (isdigit(*p)) {
1707           wnum = atoi(p);
1708         } else {
1709           wnum = -1;
1710         }
1711         switch (wnum) {
1712         case 0: /* FICS only, actually */
1713         case 1:
1714           /* Castling legal even if K starts on d-file */
1715           v = VariantWildCastle;
1716           break;
1717         case 2:
1718         case 3:
1719         case 4:
1720           /* Castling illegal even if K & R happen to start in
1721              normal positions. */
1722           v = VariantNoCastle;
1723           break;
1724         case 5:
1725         case 7:
1726         case 8:
1727         case 10:
1728         case 11:
1729         case 12:
1730         case 13:
1731         case 14:
1732         case 15:
1733         case 18:
1734         case 19:
1735           /* Castling legal iff K & R start in normal positions */
1736           v = VariantNormal;
1737           break;
1738         case 6:
1739         case 20:
1740         case 21:
1741           /* Special wilds for position setup; unclear what to do here */
1742           v = VariantLoadable;
1743           break;
1744         case 9:
1745           /* Bizarre ICC game */
1746           v = VariantTwoKings;
1747           break;
1748         case 16:
1749           v = VariantKriegspiel;
1750           break;
1751         case 17:
1752           v = VariantLosers;
1753           break;
1754         case 22:
1755           v = VariantFischeRandom;
1756           break;
1757         case 23:
1758           v = VariantCrazyhouse;
1759           break;
1760         case 24:
1761           v = VariantBughouse;
1762           break;
1763         case 25:
1764           v = Variant3Check;
1765           break;
1766         case 26:
1767           /* Not quite the same as FICS suicide! */
1768           v = VariantGiveaway;
1769           break;
1770         case 27:
1771           v = VariantAtomic;
1772           break;
1773         case 28:
1774           v = VariantShatranj;
1775           break;
1776
1777         /* Temporary names for future ICC types.  The name *will* change in
1778            the next xboard/WinBoard release after ICC defines it. */
1779         case 29:
1780           v = Variant29;
1781           break;
1782         case 30:
1783           v = Variant30;
1784           break;
1785         case 31:
1786           v = Variant31;
1787           break;
1788         case 32:
1789           v = Variant32;
1790           break;
1791         case 33:
1792           v = Variant33;
1793           break;
1794         case 34:
1795           v = Variant34;
1796           break;
1797         case 35:
1798           v = Variant35;
1799           break;
1800         case 36:
1801           v = Variant36;
1802           break;
1803         case 37:
1804           v = VariantShogi;
1805           break;
1806         case 38:
1807           v = VariantXiangqi;
1808           break;
1809         case 39:
1810           v = VariantCourier;
1811           break;
1812         case 40:
1813           v = VariantGothic;
1814           break;
1815         case 41:
1816           v = VariantCapablanca;
1817           break;
1818         case 42:
1819           v = VariantKnightmate;
1820           break;
1821         case 43:
1822           v = VariantFairy;
1823           break;
1824         case 44:
1825           v = VariantCylinder;
1826           break;
1827         case 45:
1828           v = VariantFalcon;
1829           break;
1830         case 46:
1831           v = VariantCapaRandom;
1832           break;
1833         case 47:
1834           v = VariantBerolina;
1835           break;
1836         case 48:
1837           v = VariantJanus;
1838           break;
1839         case 49:
1840           v = VariantSuper;
1841           break;
1842         case 50:
1843           v = VariantGreat;
1844           break;
1845         case -1:
1846           /* Found "wild" or "w" in the string but no number;
1847              must assume it's normal chess. */
1848           v = VariantNormal;
1849           break;
1850         default:
1851           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1852           if( (len > MSG_SIZ) && appData.debugMode )
1853             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1854
1855           DisplayError(buf, 0);
1856           v = VariantUnknown;
1857           break;
1858         }
1859       }
1860     }
1861     if (appData.debugMode) {
1862       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1863               e, wnum, VariantName(v));
1864     }
1865     return v;
1866 }
1867
1868 static int leftover_start = 0, leftover_len = 0;
1869 char star_match[STAR_MATCH_N][MSG_SIZ];
1870
1871 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1872    advance *index beyond it, and set leftover_start to the new value of
1873    *index; else return FALSE.  If pattern contains the character '*', it
1874    matches any sequence of characters not containing '\r', '\n', or the
1875    character following the '*' (if any), and the matched sequence(s) are
1876    copied into star_match.
1877    */
1878 int
1879 looking_at(buf, index, pattern)
1880      char *buf;
1881      int *index;
1882      char *pattern;
1883 {
1884     char *bufp = &buf[*index], *patternp = pattern;
1885     int star_count = 0;
1886     char *matchp = star_match[0];
1887
1888     for (;;) {
1889         if (*patternp == NULLCHAR) {
1890             *index = leftover_start = bufp - buf;
1891             *matchp = NULLCHAR;
1892             return TRUE;
1893         }
1894         if (*bufp == NULLCHAR) return FALSE;
1895         if (*patternp == '*') {
1896             if (*bufp == *(patternp + 1)) {
1897                 *matchp = NULLCHAR;
1898                 matchp = star_match[++star_count];
1899                 patternp += 2;
1900                 bufp++;
1901                 continue;
1902             } else if (*bufp == '\n' || *bufp == '\r') {
1903                 patternp++;
1904                 if (*patternp == NULLCHAR)
1905                   continue;
1906                 else
1907                   return FALSE;
1908             } else {
1909                 *matchp++ = *bufp++;
1910                 continue;
1911             }
1912         }
1913         if (*patternp != *bufp) return FALSE;
1914         patternp++;
1915         bufp++;
1916     }
1917 }
1918
1919 void
1920 SendToPlayer(data, length)
1921      char *data;
1922      int length;
1923 {
1924     int error, outCount;
1925     outCount = OutputToProcess(NoProc, data, length, &error);
1926     if (outCount < length) {
1927         DisplayFatalError(_("Error writing to display"), error, 1);
1928     }
1929 }
1930
1931 void
1932 PackHolding(packed, holding)
1933      char packed[];
1934      char *holding;
1935 {
1936     char *p = holding;
1937     char *q = packed;
1938     int runlength = 0;
1939     int curr = 9999;
1940     do {
1941         if (*p == curr) {
1942             runlength++;
1943         } else {
1944             switch (runlength) {
1945               case 0:
1946                 break;
1947               case 1:
1948                 *q++ = curr;
1949                 break;
1950               case 2:
1951                 *q++ = curr;
1952                 *q++ = curr;
1953                 break;
1954               default:
1955                 sprintf(q, "%d", runlength);
1956                 while (*q) q++;
1957                 *q++ = curr;
1958                 break;
1959             }
1960             runlength = 1;
1961             curr = *p;
1962         }
1963     } while (*p++);
1964     *q = NULLCHAR;
1965 }
1966
1967 /* Telnet protocol requests from the front end */
1968 void
1969 TelnetRequest(ddww, option)
1970      unsigned char ddww, option;
1971 {
1972     unsigned char msg[3];
1973     int outCount, outError;
1974
1975     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1976
1977     if (appData.debugMode) {
1978         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1979         switch (ddww) {
1980           case TN_DO:
1981             ddwwStr = "DO";
1982             break;
1983           case TN_DONT:
1984             ddwwStr = "DONT";
1985             break;
1986           case TN_WILL:
1987             ddwwStr = "WILL";
1988             break;
1989           case TN_WONT:
1990             ddwwStr = "WONT";
1991             break;
1992           default:
1993             ddwwStr = buf1;
1994             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1995             break;
1996         }
1997         switch (option) {
1998           case TN_ECHO:
1999             optionStr = "ECHO";
2000             break;
2001           default:
2002             optionStr = buf2;
2003             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2004             break;
2005         }
2006         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2007     }
2008     msg[0] = TN_IAC;
2009     msg[1] = ddww;
2010     msg[2] = option;
2011     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2012     if (outCount < 3) {
2013         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2014     }
2015 }
2016
2017 void
2018 DoEcho()
2019 {
2020     if (!appData.icsActive) return;
2021     TelnetRequest(TN_DO, TN_ECHO);
2022 }
2023
2024 void
2025 DontEcho()
2026 {
2027     if (!appData.icsActive) return;
2028     TelnetRequest(TN_DONT, TN_ECHO);
2029 }
2030
2031 void
2032 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2033 {
2034     /* put the holdings sent to us by the server on the board holdings area */
2035     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2036     char p;
2037     ChessSquare piece;
2038
2039     if(gameInfo.holdingsWidth < 2)  return;
2040     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2041         return; // prevent overwriting by pre-board holdings
2042
2043     if( (int)lowestPiece >= BlackPawn ) {
2044         holdingsColumn = 0;
2045         countsColumn = 1;
2046         holdingsStartRow = BOARD_HEIGHT-1;
2047         direction = -1;
2048     } else {
2049         holdingsColumn = BOARD_WIDTH-1;
2050         countsColumn = BOARD_WIDTH-2;
2051         holdingsStartRow = 0;
2052         direction = 1;
2053     }
2054
2055     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2056         board[i][holdingsColumn] = EmptySquare;
2057         board[i][countsColumn]   = (ChessSquare) 0;
2058     }
2059     while( (p=*holdings++) != NULLCHAR ) {
2060         piece = CharToPiece( ToUpper(p) );
2061         if(piece == EmptySquare) continue;
2062         /*j = (int) piece - (int) WhitePawn;*/
2063         j = PieceToNumber(piece);
2064         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2065         if(j < 0) continue;               /* should not happen */
2066         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2067         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2068         board[holdingsStartRow+j*direction][countsColumn]++;
2069     }
2070 }
2071
2072
2073 void
2074 VariantSwitch(Board board, VariantClass newVariant)
2075 {
2076    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2077    static Board oldBoard;
2078
2079    startedFromPositionFile = FALSE;
2080    if(gameInfo.variant == newVariant) return;
2081
2082    /* [HGM] This routine is called each time an assignment is made to
2083     * gameInfo.variant during a game, to make sure the board sizes
2084     * are set to match the new variant. If that means adding or deleting
2085     * holdings, we shift the playing board accordingly
2086     * This kludge is needed because in ICS observe mode, we get boards
2087     * of an ongoing game without knowing the variant, and learn about the
2088     * latter only later. This can be because of the move list we requested,
2089     * in which case the game history is refilled from the beginning anyway,
2090     * but also when receiving holdings of a crazyhouse game. In the latter
2091     * case we want to add those holdings to the already received position.
2092     */
2093
2094
2095    if (appData.debugMode) {
2096      fprintf(debugFP, "Switch board from %s to %s\n",
2097              VariantName(gameInfo.variant), VariantName(newVariant));
2098      setbuf(debugFP, NULL);
2099    }
2100    shuffleOpenings = 0;       /* [HGM] shuffle */
2101    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2102    switch(newVariant)
2103      {
2104      case VariantShogi:
2105        newWidth = 9;  newHeight = 9;
2106        gameInfo.holdingsSize = 7;
2107      case VariantBughouse:
2108      case VariantCrazyhouse:
2109        newHoldingsWidth = 2; break;
2110      case VariantGreat:
2111        newWidth = 10;
2112      case VariantSuper:
2113        newHoldingsWidth = 2;
2114        gameInfo.holdingsSize = 8;
2115        break;
2116      case VariantGothic:
2117      case VariantCapablanca:
2118      case VariantCapaRandom:
2119        newWidth = 10;
2120      default:
2121        newHoldingsWidth = gameInfo.holdingsSize = 0;
2122      };
2123
2124    if(newWidth  != gameInfo.boardWidth  ||
2125       newHeight != gameInfo.boardHeight ||
2126       newHoldingsWidth != gameInfo.holdingsWidth ) {
2127
2128      /* shift position to new playing area, if needed */
2129      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2130        for(i=0; i<BOARD_HEIGHT; i++)
2131          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2132            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2133              board[i][j];
2134        for(i=0; i<newHeight; i++) {
2135          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2136          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2137        }
2138      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2139        for(i=0; i<BOARD_HEIGHT; i++)
2140          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2141            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2142              board[i][j];
2143      }
2144      gameInfo.boardWidth  = newWidth;
2145      gameInfo.boardHeight = newHeight;
2146      gameInfo.holdingsWidth = newHoldingsWidth;
2147      gameInfo.variant = newVariant;
2148      InitDrawingSizes(-2, 0);
2149    } else gameInfo.variant = newVariant;
2150    CopyBoard(oldBoard, board);   // remember correctly formatted board
2151      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2152    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2153 }
2154
2155 static int loggedOn = FALSE;
2156
2157 /*-- Game start info cache: --*/
2158 int gs_gamenum;
2159 char gs_kind[MSG_SIZ];
2160 static char player1Name[128] = "";
2161 static char player2Name[128] = "";
2162 static char cont_seq[] = "\n\\   ";
2163 static int player1Rating = -1;
2164 static int player2Rating = -1;
2165 /*----------------------------*/
2166
2167 ColorClass curColor = ColorNormal;
2168 int suppressKibitz = 0;
2169
2170 // [HGM] seekgraph
2171 Boolean soughtPending = FALSE;
2172 Boolean seekGraphUp;
2173 #define MAX_SEEK_ADS 200
2174 #define SQUARE 0x80
2175 char *seekAdList[MAX_SEEK_ADS];
2176 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2177 float tcList[MAX_SEEK_ADS];
2178 char colorList[MAX_SEEK_ADS];
2179 int nrOfSeekAds = 0;
2180 int minRating = 1010, maxRating = 2800;
2181 int hMargin = 10, vMargin = 20, h, w;
2182 extern int squareSize, lineGap;
2183
2184 void
2185 PlotSeekAd(int i)
2186 {
2187         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2188         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2189         if(r < minRating+100 && r >=0 ) r = minRating+100;
2190         if(r > maxRating) r = maxRating;
2191         if(tc < 1.) tc = 1.;
2192         if(tc > 95.) tc = 95.;
2193         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2194         y = ((double)r - minRating)/(maxRating - minRating)
2195             * (h-vMargin-squareSize/8-1) + vMargin;
2196         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2197         if(strstr(seekAdList[i], " u ")) color = 1;
2198         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2199            !strstr(seekAdList[i], "bullet") &&
2200            !strstr(seekAdList[i], "blitz") &&
2201            !strstr(seekAdList[i], "standard") ) color = 2;
2202         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2203         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2204 }
2205
2206 void
2207 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2208 {
2209         char buf[MSG_SIZ], *ext = "";
2210         VariantClass v = StringToVariant(type);
2211         if(strstr(type, "wild")) {
2212             ext = type + 4; // append wild number
2213             if(v == VariantFischeRandom) type = "chess960"; else
2214             if(v == VariantLoadable) type = "setup"; else
2215             type = VariantName(v);
2216         }
2217         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2218         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2219             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2220             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2221             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2222             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2223             seekNrList[nrOfSeekAds] = nr;
2224             zList[nrOfSeekAds] = 0;
2225             seekAdList[nrOfSeekAds++] = StrSave(buf);
2226             if(plot) PlotSeekAd(nrOfSeekAds-1);
2227         }
2228 }
2229
2230 void
2231 EraseSeekDot(int i)
2232 {
2233     int x = xList[i], y = yList[i], d=squareSize/4, k;
2234     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2235     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2236     // now replot every dot that overlapped
2237     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2238         int xx = xList[k], yy = yList[k];
2239         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2240             DrawSeekDot(xx, yy, colorList[k]);
2241     }
2242 }
2243
2244 void
2245 RemoveSeekAd(int nr)
2246 {
2247         int i;
2248         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2249             EraseSeekDot(i);
2250             if(seekAdList[i]) free(seekAdList[i]);
2251             seekAdList[i] = seekAdList[--nrOfSeekAds];
2252             seekNrList[i] = seekNrList[nrOfSeekAds];
2253             ratingList[i] = ratingList[nrOfSeekAds];
2254             colorList[i]  = colorList[nrOfSeekAds];
2255             tcList[i] = tcList[nrOfSeekAds];
2256             xList[i]  = xList[nrOfSeekAds];
2257             yList[i]  = yList[nrOfSeekAds];
2258             zList[i]  = zList[nrOfSeekAds];
2259             seekAdList[nrOfSeekAds] = NULL;
2260             break;
2261         }
2262 }
2263
2264 Boolean
2265 MatchSoughtLine(char *line)
2266 {
2267     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2268     int nr, base, inc, u=0; char dummy;
2269
2270     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2271        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2272        (u=1) &&
2273        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2274         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2275         // match: compact and save the line
2276         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2277         return TRUE;
2278     }
2279     return FALSE;
2280 }
2281
2282 int
2283 DrawSeekGraph()
2284 {
2285     int i;
2286     if(!seekGraphUp) return FALSE;
2287     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2288     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2289
2290     DrawSeekBackground(0, 0, w, h);
2291     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2292     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2293     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2294         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2295         yy = h-1-yy;
2296         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2297         if(i%500 == 0) {
2298             char buf[MSG_SIZ];
2299             snprintf(buf, MSG_SIZ, "%d", i);
2300             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2301         }
2302     }
2303     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2304     for(i=1; i<100; i+=(i<10?1:5)) {
2305         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2306         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2307         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2308             char buf[MSG_SIZ];
2309             snprintf(buf, MSG_SIZ, "%d", i);
2310             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2311         }
2312     }
2313     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2314     return TRUE;
2315 }
2316
2317 int SeekGraphClick(ClickType click, int x, int y, int moving)
2318 {
2319     static int lastDown = 0, displayed = 0, lastSecond;
2320     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2321         if(click == Release || moving) return FALSE;
2322         nrOfSeekAds = 0;
2323         soughtPending = TRUE;
2324         SendToICS(ics_prefix);
2325         SendToICS("sought\n"); // should this be "sought all"?
2326     } else { // issue challenge based on clicked ad
2327         int dist = 10000; int i, closest = 0, second = 0;
2328         for(i=0; i<nrOfSeekAds; i++) {
2329             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2330             if(d < dist) { dist = d; closest = i; }
2331             second += (d - zList[i] < 120); // count in-range ads
2332             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2333         }
2334         if(dist < 120) {
2335             char buf[MSG_SIZ];
2336             second = (second > 1);
2337             if(displayed != closest || second != lastSecond) {
2338                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2339                 lastSecond = second; displayed = closest;
2340             }
2341             if(click == Press) {
2342                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2343                 lastDown = closest;
2344                 return TRUE;
2345             } // on press 'hit', only show info
2346             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2347             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2348             SendToICS(ics_prefix);
2349             SendToICS(buf);
2350             return TRUE; // let incoming board of started game pop down the graph
2351         } else if(click == Release) { // release 'miss' is ignored
2352             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2353             if(moving == 2) { // right up-click
2354                 nrOfSeekAds = 0; // refresh graph
2355                 soughtPending = TRUE;
2356                 SendToICS(ics_prefix);
2357                 SendToICS("sought\n"); // should this be "sought all"?
2358             }
2359             return TRUE;
2360         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2361         // press miss or release hit 'pop down' seek graph
2362         seekGraphUp = FALSE;
2363         DrawPosition(TRUE, NULL);
2364     }
2365     return TRUE;
2366 }
2367
2368 void
2369 read_from_ics(isr, closure, data, count, error)
2370      InputSourceRef isr;
2371      VOIDSTAR closure;
2372      char *data;
2373      int count;
2374      int error;
2375 {
2376 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2377 #define STARTED_NONE 0
2378 #define STARTED_MOVES 1
2379 #define STARTED_BOARD 2
2380 #define STARTED_OBSERVE 3
2381 #define STARTED_HOLDINGS 4
2382 #define STARTED_CHATTER 5
2383 #define STARTED_COMMENT 6
2384 #define STARTED_MOVES_NOHIDE 7
2385
2386     static int started = STARTED_NONE;
2387     static char parse[20000];
2388     static int parse_pos = 0;
2389     static char buf[BUF_SIZE + 1];
2390     static int firstTime = TRUE, intfSet = FALSE;
2391     static ColorClass prevColor = ColorNormal;
2392     static int savingComment = FALSE;
2393     static int cmatch = 0; // continuation sequence match
2394     char *bp;
2395     char str[MSG_SIZ];
2396     int i, oldi;
2397     int buf_len;
2398     int next_out;
2399     int tkind;
2400     int backup;    /* [DM] For zippy color lines */
2401     char *p;
2402     char talker[MSG_SIZ]; // [HGM] chat
2403     int channel;
2404
2405     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2406
2407     if (appData.debugMode) {
2408       if (!error) {
2409         fprintf(debugFP, "<ICS: ");
2410         show_bytes(debugFP, data, count);
2411         fprintf(debugFP, "\n");
2412       }
2413     }
2414
2415     if (appData.debugMode) { int f = forwardMostMove;
2416         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2417                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2418                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2419     }
2420     if (count > 0) {
2421         /* If last read ended with a partial line that we couldn't parse,
2422            prepend it to the new read and try again. */
2423         if (leftover_len > 0) {
2424             for (i=0; i<leftover_len; i++)
2425               buf[i] = buf[leftover_start + i];
2426         }
2427
2428     /* copy new characters into the buffer */
2429     bp = buf + leftover_len;
2430     buf_len=leftover_len;
2431     for (i=0; i<count; i++)
2432     {
2433         // ignore these
2434         if (data[i] == '\r')
2435             continue;
2436
2437         // join lines split by ICS?
2438         if (!appData.noJoin)
2439         {
2440             /*
2441                 Joining just consists of finding matches against the
2442                 continuation sequence, and discarding that sequence
2443                 if found instead of copying it.  So, until a match
2444                 fails, there's nothing to do since it might be the
2445                 complete sequence, and thus, something we don't want
2446                 copied.
2447             */
2448             if (data[i] == cont_seq[cmatch])
2449             {
2450                 cmatch++;
2451                 if (cmatch == strlen(cont_seq))
2452                 {
2453                     cmatch = 0; // complete match.  just reset the counter
2454
2455                     /*
2456                         it's possible for the ICS to not include the space
2457                         at the end of the last word, making our [correct]
2458                         join operation fuse two separate words.  the server
2459                         does this when the space occurs at the width setting.
2460                     */
2461                     if (!buf_len || buf[buf_len-1] != ' ')
2462                     {
2463                         *bp++ = ' ';
2464                         buf_len++;
2465                     }
2466                 }
2467                 continue;
2468             }
2469             else if (cmatch)
2470             {
2471                 /*
2472                     match failed, so we have to copy what matched before
2473                     falling through and copying this character.  In reality,
2474                     this will only ever be just the newline character, but
2475                     it doesn't hurt to be precise.
2476                 */
2477                 strncpy(bp, cont_seq, cmatch);
2478                 bp += cmatch;
2479                 buf_len += cmatch;
2480                 cmatch = 0;
2481             }
2482         }
2483
2484         // copy this char
2485         *bp++ = data[i];
2486         buf_len++;
2487     }
2488
2489         buf[buf_len] = NULLCHAR;
2490 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2491         next_out = 0;
2492         leftover_start = 0;
2493
2494         i = 0;
2495         while (i < buf_len) {
2496             /* Deal with part of the TELNET option negotiation
2497                protocol.  We refuse to do anything beyond the
2498                defaults, except that we allow the WILL ECHO option,
2499                which ICS uses to turn off password echoing when we are
2500                directly connected to it.  We reject this option
2501                if localLineEditing mode is on (always on in xboard)
2502                and we are talking to port 23, which might be a real
2503                telnet server that will try to keep WILL ECHO on permanently.
2504              */
2505             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2506                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2507                 unsigned char option;
2508                 oldi = i;
2509                 switch ((unsigned char) buf[++i]) {
2510                   case TN_WILL:
2511                     if (appData.debugMode)
2512                       fprintf(debugFP, "\n<WILL ");
2513                     switch (option = (unsigned char) buf[++i]) {
2514                       case TN_ECHO:
2515                         if (appData.debugMode)
2516                           fprintf(debugFP, "ECHO ");
2517                         /* Reply only if this is a change, according
2518                            to the protocol rules. */
2519                         if (remoteEchoOption) break;
2520                         if (appData.localLineEditing &&
2521                             atoi(appData.icsPort) == TN_PORT) {
2522                             TelnetRequest(TN_DONT, TN_ECHO);
2523                         } else {
2524                             EchoOff();
2525                             TelnetRequest(TN_DO, TN_ECHO);
2526                             remoteEchoOption = TRUE;
2527                         }
2528                         break;
2529                       default:
2530                         if (appData.debugMode)
2531                           fprintf(debugFP, "%d ", option);
2532                         /* Whatever this is, we don't want it. */
2533                         TelnetRequest(TN_DONT, option);
2534                         break;
2535                     }
2536                     break;
2537                   case TN_WONT:
2538                     if (appData.debugMode)
2539                       fprintf(debugFP, "\n<WONT ");
2540                     switch (option = (unsigned char) buf[++i]) {
2541                       case TN_ECHO:
2542                         if (appData.debugMode)
2543                           fprintf(debugFP, "ECHO ");
2544                         /* Reply only if this is a change, according
2545                            to the protocol rules. */
2546                         if (!remoteEchoOption) break;
2547                         EchoOn();
2548                         TelnetRequest(TN_DONT, TN_ECHO);
2549                         remoteEchoOption = FALSE;
2550                         break;
2551                       default:
2552                         if (appData.debugMode)
2553                           fprintf(debugFP, "%d ", (unsigned char) option);
2554                         /* Whatever this is, it must already be turned
2555                            off, because we never agree to turn on
2556                            anything non-default, so according to the
2557                            protocol rules, we don't reply. */
2558                         break;
2559                     }
2560                     break;
2561                   case TN_DO:
2562                     if (appData.debugMode)
2563                       fprintf(debugFP, "\n<DO ");
2564                     switch (option = (unsigned char) buf[++i]) {
2565                       default:
2566                         /* Whatever this is, we refuse to do it. */
2567                         if (appData.debugMode)
2568                           fprintf(debugFP, "%d ", option);
2569                         TelnetRequest(TN_WONT, option);
2570                         break;
2571                     }
2572                     break;
2573                   case TN_DONT:
2574                     if (appData.debugMode)
2575                       fprintf(debugFP, "\n<DONT ");
2576                     switch (option = (unsigned char) buf[++i]) {
2577                       default:
2578                         if (appData.debugMode)
2579                           fprintf(debugFP, "%d ", option);
2580                         /* Whatever this is, we are already not doing
2581                            it, because we never agree to do anything
2582                            non-default, so according to the protocol
2583                            rules, we don't reply. */
2584                         break;
2585                     }
2586                     break;
2587                   case TN_IAC:
2588                     if (appData.debugMode)
2589                       fprintf(debugFP, "\n<IAC ");
2590                     /* Doubled IAC; pass it through */
2591                     i--;
2592                     break;
2593                   default:
2594                     if (appData.debugMode)
2595                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2596                     /* Drop all other telnet commands on the floor */
2597                     break;
2598                 }
2599                 if (oldi > next_out)
2600                   SendToPlayer(&buf[next_out], oldi - next_out);
2601                 if (++i > next_out)
2602                   next_out = i;
2603                 continue;
2604             }
2605
2606             /* OK, this at least will *usually* work */
2607             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2608                 loggedOn = TRUE;
2609             }
2610
2611             if (loggedOn && !intfSet) {
2612                 if (ics_type == ICS_ICC) {
2613                   snprintf(str, MSG_SIZ,
2614                           "/set-quietly interface %s\n/set-quietly style 12\n",
2615                           programVersion);
2616                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2617                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2618                 } else if (ics_type == ICS_CHESSNET) {
2619                   snprintf(str, MSG_SIZ, "/style 12\n");
2620                 } else {
2621                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2622                   strcat(str, programVersion);
2623                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2624                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2625                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2626 #ifdef WIN32
2627                   strcat(str, "$iset nohighlight 1\n");
2628 #endif
2629                   strcat(str, "$iset lock 1\n$style 12\n");
2630                 }
2631                 SendToICS(str);
2632                 NotifyFrontendLogin();
2633                 intfSet = TRUE;
2634             }
2635
2636             if (started == STARTED_COMMENT) {
2637                 /* Accumulate characters in comment */
2638                 parse[parse_pos++] = buf[i];
2639                 if (buf[i] == '\n') {
2640                     parse[parse_pos] = NULLCHAR;
2641                     if(chattingPartner>=0) {
2642                         char mess[MSG_SIZ];
2643                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2644                         OutputChatMessage(chattingPartner, mess);
2645                         chattingPartner = -1;
2646                         next_out = i+1; // [HGM] suppress printing in ICS window
2647                     } else
2648                     if(!suppressKibitz) // [HGM] kibitz
2649                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2650                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2651                         int nrDigit = 0, nrAlph = 0, j;
2652                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2653                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2654                         parse[parse_pos] = NULLCHAR;
2655                         // try to be smart: if it does not look like search info, it should go to
2656                         // ICS interaction window after all, not to engine-output window.
2657                         for(j=0; j<parse_pos; j++) { // count letters and digits
2658                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2659                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2660                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2661                         }
2662                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2663                             int depth=0; float score;
2664                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2665                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2666                                 pvInfoList[forwardMostMove-1].depth = depth;
2667                                 pvInfoList[forwardMostMove-1].score = 100*score;
2668                             }
2669                             OutputKibitz(suppressKibitz, parse);
2670                         } else {
2671                             char tmp[MSG_SIZ];
2672                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2673                             SendToPlayer(tmp, strlen(tmp));
2674                         }
2675                         next_out = i+1; // [HGM] suppress printing in ICS window
2676                     }
2677                     started = STARTED_NONE;
2678                 } else {
2679                     /* Don't match patterns against characters in comment */
2680                     i++;
2681                     continue;
2682                 }
2683             }
2684             if (started == STARTED_CHATTER) {
2685                 if (buf[i] != '\n') {
2686                     /* Don't match patterns against characters in chatter */
2687                     i++;
2688                     continue;
2689                 }
2690                 started = STARTED_NONE;
2691                 if(suppressKibitz) next_out = i+1;
2692             }
2693
2694             /* Kludge to deal with rcmd protocol */
2695             if (firstTime && looking_at(buf, &i, "\001*")) {
2696                 DisplayFatalError(&buf[1], 0, 1);
2697                 continue;
2698             } else {
2699                 firstTime = FALSE;
2700             }
2701
2702             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2703                 ics_type = ICS_ICC;
2704                 ics_prefix = "/";
2705                 if (appData.debugMode)
2706                   fprintf(debugFP, "ics_type %d\n", ics_type);
2707                 continue;
2708             }
2709             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2710                 ics_type = ICS_FICS;
2711                 ics_prefix = "$";
2712                 if (appData.debugMode)
2713                   fprintf(debugFP, "ics_type %d\n", ics_type);
2714                 continue;
2715             }
2716             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2717                 ics_type = ICS_CHESSNET;
2718                 ics_prefix = "/";
2719                 if (appData.debugMode)
2720                   fprintf(debugFP, "ics_type %d\n", ics_type);
2721                 continue;
2722             }
2723
2724             if (!loggedOn &&
2725                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2726                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2727                  looking_at(buf, &i, "will be \"*\""))) {
2728               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2729               continue;
2730             }
2731
2732             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2733               char buf[MSG_SIZ];
2734               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2735               DisplayIcsInteractionTitle(buf);
2736               have_set_title = TRUE;
2737             }
2738
2739             /* skip finger notes */
2740             if (started == STARTED_NONE &&
2741                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2742                  (buf[i] == '1' && buf[i+1] == '0')) &&
2743                 buf[i+2] == ':' && buf[i+3] == ' ') {
2744               started = STARTED_CHATTER;
2745               i += 3;
2746               continue;
2747             }
2748
2749             oldi = i;
2750             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2751             if(appData.seekGraph) {
2752                 if(soughtPending && MatchSoughtLine(buf+i)) {
2753                     i = strstr(buf+i, "rated") - buf;
2754                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2755                     next_out = leftover_start = i;
2756                     started = STARTED_CHATTER;
2757                     suppressKibitz = TRUE;
2758                     continue;
2759                 }
2760                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2761                         && looking_at(buf, &i, "* ads displayed")) {
2762                     soughtPending = FALSE;
2763                     seekGraphUp = TRUE;
2764                     DrawSeekGraph();
2765                     continue;
2766                 }
2767                 if(appData.autoRefresh) {
2768                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2769                         int s = (ics_type == ICS_ICC); // ICC format differs
2770                         if(seekGraphUp)
2771                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2772                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2773                         looking_at(buf, &i, "*% "); // eat prompt
2774                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2775                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2776                         next_out = i; // suppress
2777                         continue;
2778                     }
2779                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2780                         char *p = star_match[0];
2781                         while(*p) {
2782                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2783                             while(*p && *p++ != ' '); // next
2784                         }
2785                         looking_at(buf, &i, "*% "); // eat prompt
2786                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2787                         next_out = i;
2788                         continue;
2789                     }
2790                 }
2791             }
2792
2793             /* skip formula vars */
2794             if (started == STARTED_NONE &&
2795                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2796               started = STARTED_CHATTER;
2797               i += 3;
2798               continue;
2799             }
2800
2801             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2802             if (appData.autoKibitz && started == STARTED_NONE &&
2803                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2804                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2805                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2806                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2807                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2808                         suppressKibitz = TRUE;
2809                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2810                         next_out = i;
2811                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2812                                 && (gameMode == IcsPlayingWhite)) ||
2813                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2814                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2815                             started = STARTED_CHATTER; // own kibitz we simply discard
2816                         else {
2817                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2818                             parse_pos = 0; parse[0] = NULLCHAR;
2819                             savingComment = TRUE;
2820                             suppressKibitz = gameMode != IcsObserving ? 2 :
2821                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2822                         }
2823                         continue;
2824                 } else
2825                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2826                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2827                          && atoi(star_match[0])) {
2828                     // suppress the acknowledgements of our own autoKibitz
2829                     char *p;
2830                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2831                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2832                     SendToPlayer(star_match[0], strlen(star_match[0]));
2833                     if(looking_at(buf, &i, "*% ")) // eat prompt
2834                         suppressKibitz = FALSE;
2835                     next_out = i;
2836                     continue;
2837                 }
2838             } // [HGM] kibitz: end of patch
2839
2840             // [HGM] chat: intercept tells by users for which we have an open chat window
2841             channel = -1;
2842             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2843                                            looking_at(buf, &i, "* whispers:") ||
2844                                            looking_at(buf, &i, "* kibitzes:") ||
2845                                            looking_at(buf, &i, "* shouts:") ||
2846                                            looking_at(buf, &i, "* c-shouts:") ||
2847                                            looking_at(buf, &i, "--> * ") ||
2848                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2849                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2850                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2851                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2852                 int p;
2853                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2854                 chattingPartner = -1;
2855
2856                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2857                 for(p=0; p<MAX_CHAT; p++) {
2858                     if(channel == atoi(chatPartner[p])) {
2859                     talker[0] = '['; strcat(talker, "] ");
2860                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2861                     chattingPartner = p; break;
2862                     }
2863                 } else
2864                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2865                 for(p=0; p<MAX_CHAT; p++) {
2866                     if(!strcmp("kibitzes", chatPartner[p])) {
2867                         talker[0] = '['; strcat(talker, "] ");
2868                         chattingPartner = p; break;
2869                     }
2870                 } else
2871                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2872                 for(p=0; p<MAX_CHAT; p++) {
2873                     if(!strcmp("whispers", chatPartner[p])) {
2874                         talker[0] = '['; strcat(talker, "] ");
2875                         chattingPartner = p; break;
2876                     }
2877                 } else
2878                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2879                   if(buf[i-8] == '-' && buf[i-3] == 't')
2880                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2881                     if(!strcmp("c-shouts", chatPartner[p])) {
2882                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2883                         chattingPartner = p; break;
2884                     }
2885                   }
2886                   if(chattingPartner < 0)
2887                   for(p=0; p<MAX_CHAT; p++) {
2888                     if(!strcmp("shouts", chatPartner[p])) {
2889                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2890                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2891                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2892                         chattingPartner = p; break;
2893                     }
2894                   }
2895                 }
2896                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2897                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2898                     talker[0] = 0; Colorize(ColorTell, FALSE);
2899                     chattingPartner = p; break;
2900                 }
2901                 if(chattingPartner<0) i = oldi; else {
2902                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2903                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2904                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2905                     started = STARTED_COMMENT;
2906                     parse_pos = 0; parse[0] = NULLCHAR;
2907                     savingComment = 3 + chattingPartner; // counts as TRUE
2908                     suppressKibitz = TRUE;
2909                     continue;
2910                 }
2911             } // [HGM] chat: end of patch
2912
2913             if (appData.zippyTalk || appData.zippyPlay) {
2914                 /* [DM] Backup address for color zippy lines */
2915                 backup = i;
2916 #if ZIPPY
2917                if (loggedOn == TRUE)
2918                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2919                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2920 #endif
2921             } // [DM] 'else { ' deleted
2922                 if (
2923                     /* Regular tells and says */
2924                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2925                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2926                     looking_at(buf, &i, "* says: ") ||
2927                     /* Don't color "message" or "messages" output */
2928                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2929                     looking_at(buf, &i, "*. * at *:*: ") ||
2930                     looking_at(buf, &i, "--* (*:*): ") ||
2931                     /* Message notifications (same color as tells) */
2932                     looking_at(buf, &i, "* has left a message ") ||
2933                     looking_at(buf, &i, "* just sent you a message:\n") ||
2934                     /* Whispers and kibitzes */
2935                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2936                     looking_at(buf, &i, "* kibitzes: ") ||
2937                     /* Channel tells */
2938                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2939
2940                   if (tkind == 1 && strchr(star_match[0], ':')) {
2941                       /* Avoid "tells you:" spoofs in channels */
2942                      tkind = 3;
2943                   }
2944                   if (star_match[0][0] == NULLCHAR ||
2945                       strchr(star_match[0], ' ') ||
2946                       (tkind == 3 && strchr(star_match[1], ' '))) {
2947                     /* Reject bogus matches */
2948                     i = oldi;
2949                   } else {
2950                     if (appData.colorize) {
2951                       if (oldi > next_out) {
2952                         SendToPlayer(&buf[next_out], oldi - next_out);
2953                         next_out = oldi;
2954                       }
2955                       switch (tkind) {
2956                       case 1:
2957                         Colorize(ColorTell, FALSE);
2958                         curColor = ColorTell;
2959                         break;
2960                       case 2:
2961                         Colorize(ColorKibitz, FALSE);
2962                         curColor = ColorKibitz;
2963                         break;
2964                       case 3:
2965                         p = strrchr(star_match[1], '(');
2966                         if (p == NULL) {
2967                           p = star_match[1];
2968                         } else {
2969                           p++;
2970                         }
2971                         if (atoi(p) == 1) {
2972                           Colorize(ColorChannel1, FALSE);
2973                           curColor = ColorChannel1;
2974                         } else {
2975                           Colorize(ColorChannel, FALSE);
2976                           curColor = ColorChannel;
2977                         }
2978                         break;
2979                       case 5:
2980                         curColor = ColorNormal;
2981                         break;
2982                       }
2983                     }
2984                     if (started == STARTED_NONE && appData.autoComment &&
2985                         (gameMode == IcsObserving ||
2986                          gameMode == IcsPlayingWhite ||
2987                          gameMode == IcsPlayingBlack)) {
2988                       parse_pos = i - oldi;
2989                       memcpy(parse, &buf[oldi], parse_pos);
2990                       parse[parse_pos] = NULLCHAR;
2991                       started = STARTED_COMMENT;
2992                       savingComment = TRUE;
2993                     } else {
2994                       started = STARTED_CHATTER;
2995                       savingComment = FALSE;
2996                     }
2997                     loggedOn = TRUE;
2998                     continue;
2999                   }
3000                 }
3001
3002                 if (looking_at(buf, &i, "* s-shouts: ") ||
3003                     looking_at(buf, &i, "* c-shouts: ")) {
3004                     if (appData.colorize) {
3005                         if (oldi > next_out) {
3006                             SendToPlayer(&buf[next_out], oldi - next_out);
3007                             next_out = oldi;
3008                         }
3009                         Colorize(ColorSShout, FALSE);
3010                         curColor = ColorSShout;
3011                     }
3012                     loggedOn = TRUE;
3013                     started = STARTED_CHATTER;
3014                     continue;
3015                 }
3016
3017                 if (looking_at(buf, &i, "--->")) {
3018                     loggedOn = TRUE;
3019                     continue;
3020                 }
3021
3022                 if (looking_at(buf, &i, "* shouts: ") ||
3023                     looking_at(buf, &i, "--> ")) {
3024                     if (appData.colorize) {
3025                         if (oldi > next_out) {
3026                             SendToPlayer(&buf[next_out], oldi - next_out);
3027                             next_out = oldi;
3028                         }
3029                         Colorize(ColorShout, FALSE);
3030                         curColor = ColorShout;
3031                     }
3032                     loggedOn = TRUE;
3033                     started = STARTED_CHATTER;
3034                     continue;
3035                 }
3036
3037                 if (looking_at( buf, &i, "Challenge:")) {
3038                     if (appData.colorize) {
3039                         if (oldi > next_out) {
3040                             SendToPlayer(&buf[next_out], oldi - next_out);
3041                             next_out = oldi;
3042                         }
3043                         Colorize(ColorChallenge, FALSE);
3044                         curColor = ColorChallenge;
3045                     }
3046                     loggedOn = TRUE;
3047                     continue;
3048                 }
3049
3050                 if (looking_at(buf, &i, "* offers you") ||
3051                     looking_at(buf, &i, "* offers to be") ||
3052                     looking_at(buf, &i, "* would like to") ||
3053                     looking_at(buf, &i, "* requests to") ||
3054                     looking_at(buf, &i, "Your opponent offers") ||
3055                     looking_at(buf, &i, "Your opponent requests")) {
3056
3057                     if (appData.colorize) {
3058                         if (oldi > next_out) {
3059                             SendToPlayer(&buf[next_out], oldi - next_out);
3060                             next_out = oldi;
3061                         }
3062                         Colorize(ColorRequest, FALSE);
3063                         curColor = ColorRequest;
3064                     }
3065                     continue;
3066                 }
3067
3068                 if (looking_at(buf, &i, "* (*) seeking")) {
3069                     if (appData.colorize) {
3070                         if (oldi > next_out) {
3071                             SendToPlayer(&buf[next_out], oldi - next_out);
3072                             next_out = oldi;
3073                         }
3074                         Colorize(ColorSeek, FALSE);
3075                         curColor = ColorSeek;
3076                     }
3077                     continue;
3078             }
3079
3080             if (looking_at(buf, &i, "\\   ")) {
3081                 if (prevColor != ColorNormal) {
3082                     if (oldi > next_out) {
3083                         SendToPlayer(&buf[next_out], oldi - next_out);
3084                         next_out = oldi;
3085                     }
3086                     Colorize(prevColor, TRUE);
3087                     curColor = prevColor;
3088                 }
3089                 if (savingComment) {
3090                     parse_pos = i - oldi;
3091                     memcpy(parse, &buf[oldi], parse_pos);
3092                     parse[parse_pos] = NULLCHAR;
3093                     started = STARTED_COMMENT;
3094                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3095                         chattingPartner = savingComment - 3; // kludge to remember the box
3096                 } else {
3097                     started = STARTED_CHATTER;
3098                 }
3099                 continue;
3100             }
3101
3102             if (looking_at(buf, &i, "Black Strength :") ||
3103                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3104                 looking_at(buf, &i, "<10>") ||
3105                 looking_at(buf, &i, "#@#")) {
3106                 /* Wrong board style */
3107                 loggedOn = TRUE;
3108                 SendToICS(ics_prefix);
3109                 SendToICS("set style 12\n");
3110                 SendToICS(ics_prefix);
3111                 SendToICS("refresh\n");
3112                 continue;
3113             }
3114
3115             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3116                 ICSInitScript();
3117                 have_sent_ICS_logon = 1;
3118                 /* if we don't send the login/password via icsLogon, use special readline
3119                    code for it */
3120                 if (strlen(appData.icsLogon)==0)
3121                   {
3122                     sending_ICS_password = 0; // in case we come back to login
3123                     sending_ICS_login = 1;
3124                   };
3125                 continue;
3126             }
3127             /* need to shadow the password */
3128             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3129               /* if we don't send the login/password via icsLogon, use special readline
3130                  code for it */
3131               if (strlen(appData.icsLogon)==0)
3132                 sending_ICS_password = 1;
3133               continue;
3134             }
3135
3136             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3137                 (looking_at(buf, &i, "\n<12> ") ||
3138                  looking_at(buf, &i, "<12> "))) {
3139                 loggedOn = TRUE;
3140                 if (oldi > next_out) {
3141                     SendToPlayer(&buf[next_out], oldi - next_out);
3142                 }
3143                 next_out = i;
3144                 started = STARTED_BOARD;
3145                 parse_pos = 0;
3146                 continue;
3147             }
3148
3149             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3150                 looking_at(buf, &i, "<b1> ")) {
3151                 if (oldi > next_out) {
3152                     SendToPlayer(&buf[next_out], oldi - next_out);
3153                 }
3154                 next_out = i;
3155                 started = STARTED_HOLDINGS;
3156                 parse_pos = 0;
3157                 continue;
3158             }
3159
3160             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3161                 loggedOn = TRUE;
3162                 /* Header for a move list -- first line */
3163
3164                 switch (ics_getting_history) {
3165                   case H_FALSE:
3166                     switch (gameMode) {
3167                       case IcsIdle:
3168                       case BeginningOfGame:
3169                         /* User typed "moves" or "oldmoves" while we
3170                            were idle.  Pretend we asked for these
3171                            moves and soak them up so user can step
3172                            through them and/or save them.
3173                            */
3174                         Reset(FALSE, TRUE);
3175                         gameMode = IcsObserving;
3176                         ModeHighlight();
3177                         ics_gamenum = -1;
3178                         ics_getting_history = H_GOT_UNREQ_HEADER;
3179                         break;
3180                       case EditGame: /*?*/
3181                       case EditPosition: /*?*/
3182                         /* Should above feature work in these modes too? */
3183                         /* For now it doesn't */
3184                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3185                         break;
3186                       default:
3187                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3188                         break;
3189                     }
3190                     break;
3191                   case H_REQUESTED:
3192                     /* Is this the right one? */
3193                     if (gameInfo.white && gameInfo.black &&
3194                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3195                         strcmp(gameInfo.black, star_match[2]) == 0) {
3196                         /* All is well */
3197                         ics_getting_history = H_GOT_REQ_HEADER;
3198                     }
3199                     break;
3200                   case H_GOT_REQ_HEADER:
3201                   case H_GOT_UNREQ_HEADER:
3202                   case H_GOT_UNWANTED_HEADER:
3203                   case H_GETTING_MOVES:
3204                     /* Should not happen */
3205                     DisplayError(_("Error gathering move list: two headers"), 0);
3206                     ics_getting_history = H_FALSE;
3207                     break;
3208                 }
3209
3210                 /* Save player ratings into gameInfo if needed */
3211                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3212                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3213                     (gameInfo.whiteRating == -1 ||
3214                      gameInfo.blackRating == -1)) {
3215
3216                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3217                     gameInfo.blackRating = string_to_rating(star_match[3]);
3218                     if (appData.debugMode)
3219                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3220                               gameInfo.whiteRating, gameInfo.blackRating);
3221                 }
3222                 continue;
3223             }
3224
3225             if (looking_at(buf, &i,
3226               "* * match, initial time: * minute*, increment: * second")) {
3227                 /* Header for a move list -- second line */
3228                 /* Initial board will follow if this is a wild game */
3229                 if (gameInfo.event != NULL) free(gameInfo.event);
3230                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3231                 gameInfo.event = StrSave(str);
3232                 /* [HGM] we switched variant. Translate boards if needed. */
3233                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3234                 continue;
3235             }
3236
3237             if (looking_at(buf, &i, "Move  ")) {
3238                 /* Beginning of a move list */
3239                 switch (ics_getting_history) {
3240                   case H_FALSE:
3241                     /* Normally should not happen */
3242                     /* Maybe user hit reset while we were parsing */
3243                     break;
3244                   case H_REQUESTED:
3245                     /* Happens if we are ignoring a move list that is not
3246                      * the one we just requested.  Common if the user
3247                      * tries to observe two games without turning off
3248                      * getMoveList */
3249                     break;
3250                   case H_GETTING_MOVES:
3251                     /* Should not happen */
3252                     DisplayError(_("Error gathering move list: nested"), 0);
3253                     ics_getting_history = H_FALSE;
3254                     break;
3255                   case H_GOT_REQ_HEADER:
3256                     ics_getting_history = H_GETTING_MOVES;
3257                     started = STARTED_MOVES;
3258                     parse_pos = 0;
3259                     if (oldi > next_out) {
3260                         SendToPlayer(&buf[next_out], oldi - next_out);
3261                     }
3262                     break;
3263                   case H_GOT_UNREQ_HEADER:
3264                     ics_getting_history = H_GETTING_MOVES;
3265                     started = STARTED_MOVES_NOHIDE;
3266                     parse_pos = 0;
3267                     break;
3268                   case H_GOT_UNWANTED_HEADER:
3269                     ics_getting_history = H_FALSE;
3270                     break;
3271                 }
3272                 continue;
3273             }
3274
3275             if (looking_at(buf, &i, "% ") ||
3276                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3277                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3278                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3279                     soughtPending = FALSE;
3280                     seekGraphUp = TRUE;
3281                     DrawSeekGraph();
3282                 }
3283                 if(suppressKibitz) next_out = i;
3284                 savingComment = FALSE;
3285                 suppressKibitz = 0;
3286                 switch (started) {
3287                   case STARTED_MOVES:
3288                   case STARTED_MOVES_NOHIDE:
3289                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3290                     parse[parse_pos + i - oldi] = NULLCHAR;
3291                     ParseGameHistory(parse);
3292 #if ZIPPY
3293                     if (appData.zippyPlay && first.initDone) {
3294                         FeedMovesToProgram(&first, forwardMostMove);
3295                         if (gameMode == IcsPlayingWhite) {
3296                             if (WhiteOnMove(forwardMostMove)) {
3297                                 if (first.sendTime) {
3298                                   if (first.useColors) {
3299                                     SendToProgram("black\n", &first);
3300                                   }
3301                                   SendTimeRemaining(&first, TRUE);
3302                                 }
3303                                 if (first.useColors) {
3304                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3305                                 }
3306                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3307                                 first.maybeThinking = TRUE;
3308                             } else {
3309                                 if (first.usePlayother) {
3310                                   if (first.sendTime) {
3311                                     SendTimeRemaining(&first, TRUE);
3312                                   }
3313                                   SendToProgram("playother\n", &first);
3314                                   firstMove = FALSE;
3315                                 } else {
3316                                   firstMove = TRUE;
3317                                 }
3318                             }
3319                         } else if (gameMode == IcsPlayingBlack) {
3320                             if (!WhiteOnMove(forwardMostMove)) {
3321                                 if (first.sendTime) {
3322                                   if (first.useColors) {
3323                                     SendToProgram("white\n", &first);
3324                                   }
3325                                   SendTimeRemaining(&first, FALSE);
3326                                 }
3327                                 if (first.useColors) {
3328                                   SendToProgram("black\n", &first);
3329                                 }
3330                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3331                                 first.maybeThinking = TRUE;
3332                             } else {
3333                                 if (first.usePlayother) {
3334                                   if (first.sendTime) {
3335                                     SendTimeRemaining(&first, FALSE);
3336                                   }
3337                                   SendToProgram("playother\n", &first);
3338                                   firstMove = FALSE;
3339                                 } else {
3340                                   firstMove = TRUE;
3341                                 }
3342                             }
3343                         }
3344                     }
3345 #endif
3346                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3347                         /* Moves came from oldmoves or moves command
3348                            while we weren't doing anything else.
3349                            */
3350                         currentMove = forwardMostMove;
3351                         ClearHighlights();/*!!could figure this out*/
3352                         flipView = appData.flipView;
3353                         DrawPosition(TRUE, boards[currentMove]);
3354                         DisplayBothClocks();
3355                         snprintf(str, MSG_SIZ, "%s vs. %s",
3356                                 gameInfo.white, gameInfo.black);
3357                         DisplayTitle(str);
3358                         gameMode = IcsIdle;
3359                     } else {
3360                         /* Moves were history of an active game */
3361                         if (gameInfo.resultDetails != NULL) {
3362                             free(gameInfo.resultDetails);
3363                             gameInfo.resultDetails = NULL;
3364                         }
3365                     }
3366                     HistorySet(parseList, backwardMostMove,
3367                                forwardMostMove, currentMove-1);
3368                     DisplayMove(currentMove - 1);
3369                     if (started == STARTED_MOVES) next_out = i;
3370                     started = STARTED_NONE;
3371                     ics_getting_history = H_FALSE;
3372                     break;
3373
3374                   case STARTED_OBSERVE:
3375                     started = STARTED_NONE;
3376                     SendToICS(ics_prefix);
3377                     SendToICS("refresh\n");
3378                     break;
3379
3380                   default:
3381                     break;
3382                 }
3383                 if(bookHit) { // [HGM] book: simulate book reply
3384                     static char bookMove[MSG_SIZ]; // a bit generous?
3385
3386                     programStats.nodes = programStats.depth = programStats.time =
3387                     programStats.score = programStats.got_only_move = 0;
3388                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3389
3390                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3391                     strcat(bookMove, bookHit);
3392                     HandleMachineMove(bookMove, &first);
3393                 }
3394                 continue;
3395             }
3396
3397             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3398                  started == STARTED_HOLDINGS ||
3399                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3400                 /* Accumulate characters in move list or board */
3401                 parse[parse_pos++] = buf[i];
3402             }
3403
3404             /* Start of game messages.  Mostly we detect start of game
3405                when the first board image arrives.  On some versions
3406                of the ICS, though, we need to do a "refresh" after starting
3407                to observe in order to get the current board right away. */
3408             if (looking_at(buf, &i, "Adding game * to observation list")) {
3409                 started = STARTED_OBSERVE;
3410                 continue;
3411             }
3412
3413             /* Handle auto-observe */
3414             if (appData.autoObserve &&
3415                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3416                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3417                 char *player;
3418                 /* Choose the player that was highlighted, if any. */
3419                 if (star_match[0][0] == '\033' ||
3420                     star_match[1][0] != '\033') {
3421                     player = star_match[0];
3422                 } else {
3423                     player = star_match[2];
3424                 }
3425                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3426                         ics_prefix, StripHighlightAndTitle(player));
3427                 SendToICS(str);
3428
3429                 /* Save ratings from notify string */
3430                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3431                 player1Rating = string_to_rating(star_match[1]);
3432                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3433                 player2Rating = string_to_rating(star_match[3]);
3434
3435                 if (appData.debugMode)
3436                   fprintf(debugFP,
3437                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3438                           player1Name, player1Rating,
3439                           player2Name, player2Rating);
3440
3441                 continue;
3442             }
3443
3444             /* Deal with automatic examine mode after a game,
3445                and with IcsObserving -> IcsExamining transition */
3446             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3447                 looking_at(buf, &i, "has made you an examiner of game *")) {
3448
3449                 int gamenum = atoi(star_match[0]);
3450                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3451                     gamenum == ics_gamenum) {
3452                     /* We were already playing or observing this game;
3453                        no need to refetch history */
3454                     gameMode = IcsExamining;
3455                     if (pausing) {
3456                         pauseExamForwardMostMove = forwardMostMove;
3457                     } else if (currentMove < forwardMostMove) {
3458                         ForwardInner(forwardMostMove);
3459                     }
3460                 } else {
3461                     /* I don't think this case really can happen */
3462                     SendToICS(ics_prefix);
3463                     SendToICS("refresh\n");
3464                 }
3465                 continue;
3466             }
3467
3468             /* Error messages */
3469 //          if (ics_user_moved) {
3470             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3471                 if (looking_at(buf, &i, "Illegal move") ||
3472                     looking_at(buf, &i, "Not a legal move") ||
3473                     looking_at(buf, &i, "Your king is in check") ||
3474                     looking_at(buf, &i, "It isn't your turn") ||
3475                     looking_at(buf, &i, "It is not your move")) {
3476                     /* Illegal move */
3477                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3478                         currentMove = forwardMostMove-1;
3479                         DisplayMove(currentMove - 1); /* before DMError */
3480                         DrawPosition(FALSE, boards[currentMove]);
3481                         SwitchClocks(forwardMostMove-1); // [HGM] race
3482                         DisplayBothClocks();
3483                     }
3484                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3485                     ics_user_moved = 0;
3486                     continue;
3487                 }
3488             }
3489
3490             if (looking_at(buf, &i, "still have time") ||
3491                 looking_at(buf, &i, "not out of time") ||
3492                 looking_at(buf, &i, "either player is out of time") ||
3493                 looking_at(buf, &i, "has timeseal; checking")) {
3494                 /* We must have called his flag a little too soon */
3495                 whiteFlag = blackFlag = FALSE;
3496                 continue;
3497             }
3498
3499             if (looking_at(buf, &i, "added * seconds to") ||
3500                 looking_at(buf, &i, "seconds were added to")) {
3501                 /* Update the clocks */
3502                 SendToICS(ics_prefix);
3503                 SendToICS("refresh\n");
3504                 continue;
3505             }
3506
3507             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3508                 ics_clock_paused = TRUE;
3509                 StopClocks();
3510                 continue;
3511             }
3512
3513             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3514                 ics_clock_paused = FALSE;
3515                 StartClocks();
3516                 continue;
3517             }
3518
3519             /* Grab player ratings from the Creating: message.
3520                Note we have to check for the special case when
3521                the ICS inserts things like [white] or [black]. */
3522             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3523                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3524                 /* star_matches:
3525                    0    player 1 name (not necessarily white)
3526                    1    player 1 rating
3527                    2    empty, white, or black (IGNORED)
3528                    3    player 2 name (not necessarily black)
3529                    4    player 2 rating
3530
3531                    The names/ratings are sorted out when the game
3532                    actually starts (below).
3533                 */
3534                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3535                 player1Rating = string_to_rating(star_match[1]);
3536                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3537                 player2Rating = string_to_rating(star_match[4]);
3538
3539                 if (appData.debugMode)
3540                   fprintf(debugFP,
3541                           "Ratings from 'Creating:' %s %d, %s %d\n",
3542                           player1Name, player1Rating,
3543                           player2Name, player2Rating);
3544
3545                 continue;
3546             }
3547
3548             /* Improved generic start/end-of-game messages */
3549             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3550                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3551                 /* If tkind == 0: */
3552                 /* star_match[0] is the game number */
3553                 /*           [1] is the white player's name */
3554                 /*           [2] is the black player's name */
3555                 /* For end-of-game: */
3556                 /*           [3] is the reason for the game end */
3557                 /*           [4] is a PGN end game-token, preceded by " " */
3558                 /* For start-of-game: */
3559                 /*           [3] begins with "Creating" or "Continuing" */
3560                 /*           [4] is " *" or empty (don't care). */
3561                 int gamenum = atoi(star_match[0]);
3562                 char *whitename, *blackname, *why, *endtoken;
3563                 ChessMove endtype = EndOfFile;
3564
3565                 if (tkind == 0) {
3566                   whitename = star_match[1];
3567                   blackname = star_match[2];
3568                   why = star_match[3];
3569                   endtoken = star_match[4];
3570                 } else {
3571                   whitename = star_match[1];
3572                   blackname = star_match[3];
3573                   why = star_match[5];
3574                   endtoken = star_match[6];
3575                 }
3576
3577                 /* Game start messages */
3578                 if (strncmp(why, "Creating ", 9) == 0 ||
3579                     strncmp(why, "Continuing ", 11) == 0) {
3580                     gs_gamenum = gamenum;
3581                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3582                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3583 #if ZIPPY
3584                     if (appData.zippyPlay) {
3585                         ZippyGameStart(whitename, blackname);
3586                     }
3587 #endif /*ZIPPY*/
3588                     partnerBoardValid = FALSE; // [HGM] bughouse
3589                     continue;
3590                 }
3591
3592                 /* Game end messages */
3593                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3594                     ics_gamenum != gamenum) {
3595                     continue;
3596                 }
3597                 while (endtoken[0] == ' ') endtoken++;
3598                 switch (endtoken[0]) {
3599                   case '*':
3600                   default:
3601                     endtype = GameUnfinished;
3602                     break;
3603                   case '0':
3604                     endtype = BlackWins;
3605                     break;
3606                   case '1':
3607                     if (endtoken[1] == '/')
3608                       endtype = GameIsDrawn;
3609                     else
3610                       endtype = WhiteWins;
3611                     break;
3612                 }
3613                 GameEnds(endtype, why, GE_ICS);
3614 #if ZIPPY
3615                 if (appData.zippyPlay && first.initDone) {
3616                     ZippyGameEnd(endtype, why);
3617                     if (first.pr == NULL) {
3618                       /* Start the next process early so that we'll
3619                          be ready for the next challenge */
3620                       StartChessProgram(&first);
3621                     }
3622                     /* Send "new" early, in case this command takes
3623                        a long time to finish, so that we'll be ready
3624                        for the next challenge. */
3625                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3626                     Reset(TRUE, TRUE);
3627                 }
3628 #endif /*ZIPPY*/
3629                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3630                 continue;
3631             }
3632
3633             if (looking_at(buf, &i, "Removing game * from observation") ||
3634                 looking_at(buf, &i, "no longer observing game *") ||
3635                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3636                 if (gameMode == IcsObserving &&
3637                     atoi(star_match[0]) == ics_gamenum)
3638                   {
3639                       /* icsEngineAnalyze */
3640                       if (appData.icsEngineAnalyze) {
3641                             ExitAnalyzeMode();
3642                             ModeHighlight();
3643                       }
3644                       StopClocks();
3645                       gameMode = IcsIdle;
3646                       ics_gamenum = -1;
3647                       ics_user_moved = FALSE;
3648                   }
3649                 continue;
3650             }
3651
3652             if (looking_at(buf, &i, "no longer examining game *")) {
3653                 if (gameMode == IcsExamining &&
3654                     atoi(star_match[0]) == ics_gamenum)
3655                   {
3656                       gameMode = IcsIdle;
3657                       ics_gamenum = -1;
3658                       ics_user_moved = FALSE;
3659                   }
3660                 continue;
3661             }
3662
3663             /* Advance leftover_start past any newlines we find,
3664                so only partial lines can get reparsed */
3665             if (looking_at(buf, &i, "\n")) {
3666                 prevColor = curColor;
3667                 if (curColor != ColorNormal) {
3668                     if (oldi > next_out) {
3669                         SendToPlayer(&buf[next_out], oldi - next_out);
3670                         next_out = oldi;
3671                     }
3672                     Colorize(ColorNormal, FALSE);
3673                     curColor = ColorNormal;
3674                 }
3675                 if (started == STARTED_BOARD) {
3676                     started = STARTED_NONE;
3677                     parse[parse_pos] = NULLCHAR;
3678                     ParseBoard12(parse);
3679                     ics_user_moved = 0;
3680
3681                     /* Send premove here */
3682                     if (appData.premove) {
3683                       char str[MSG_SIZ];
3684                       if (currentMove == 0 &&
3685                           gameMode == IcsPlayingWhite &&
3686                           appData.premoveWhite) {
3687                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3688                         if (appData.debugMode)
3689                           fprintf(debugFP, "Sending premove:\n");
3690                         SendToICS(str);
3691                       } else if (currentMove == 1 &&
3692                                  gameMode == IcsPlayingBlack &&
3693                                  appData.premoveBlack) {
3694                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3695                         if (appData.debugMode)
3696                           fprintf(debugFP, "Sending premove:\n");
3697                         SendToICS(str);
3698                       } else if (gotPremove) {
3699                         gotPremove = 0;
3700                         ClearPremoveHighlights();
3701                         if (appData.debugMode)
3702                           fprintf(debugFP, "Sending premove:\n");
3703                           UserMoveEvent(premoveFromX, premoveFromY,
3704                                         premoveToX, premoveToY,
3705                                         premovePromoChar);
3706                       }
3707                     }
3708
3709                     /* Usually suppress following prompt */
3710                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3711                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3712                         if (looking_at(buf, &i, "*% ")) {
3713                             savingComment = FALSE;
3714                             suppressKibitz = 0;
3715                         }
3716                     }
3717                     next_out = i;
3718                 } else if (started == STARTED_HOLDINGS) {
3719                     int gamenum;
3720                     char new_piece[MSG_SIZ];
3721                     started = STARTED_NONE;
3722                     parse[parse_pos] = NULLCHAR;
3723                     if (appData.debugMode)
3724                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3725                                                         parse, currentMove);
3726                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3727                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3728                         if (gameInfo.variant == VariantNormal) {
3729                           /* [HGM] We seem to switch variant during a game!
3730                            * Presumably no holdings were displayed, so we have
3731                            * to move the position two files to the right to
3732                            * create room for them!
3733                            */
3734                           VariantClass newVariant;
3735                           switch(gameInfo.boardWidth) { // base guess on board width
3736                                 case 9:  newVariant = VariantShogi; break;
3737                                 case 10: newVariant = VariantGreat; break;
3738                                 default: newVariant = VariantCrazyhouse; break;
3739                           }
3740                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3741                           /* Get a move list just to see the header, which
3742                              will tell us whether this is really bug or zh */
3743                           if (ics_getting_history == H_FALSE) {
3744                             ics_getting_history = H_REQUESTED;
3745                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3746                             SendToICS(str);
3747                           }
3748                         }
3749                         new_piece[0] = NULLCHAR;
3750                         sscanf(parse, "game %d white [%s black [%s <- %s",
3751                                &gamenum, white_holding, black_holding,
3752                                new_piece);
3753                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3754                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3755                         /* [HGM] copy holdings to board holdings area */
3756                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3757                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3758                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3759 #if ZIPPY
3760                         if (appData.zippyPlay && first.initDone) {
3761                             ZippyHoldings(white_holding, black_holding,
3762                                           new_piece);
3763                         }
3764 #endif /*ZIPPY*/
3765                         if (tinyLayout || smallLayout) {
3766                             char wh[16], bh[16];
3767                             PackHolding(wh, white_holding);
3768                             PackHolding(bh, black_holding);
3769                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3770                                     gameInfo.white, gameInfo.black);
3771                         } else {
3772                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3773                                     gameInfo.white, white_holding,
3774                                     gameInfo.black, black_holding);
3775                         }
3776                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3777                         DrawPosition(FALSE, boards[currentMove]);
3778                         DisplayTitle(str);
3779                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3780                         sscanf(parse, "game %d white [%s black [%s <- %s",
3781                                &gamenum, white_holding, black_holding,
3782                                new_piece);
3783                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3784                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3785                         /* [HGM] copy holdings to partner-board holdings area */
3786                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3787                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3788                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3789                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3790                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3791                       }
3792                     }
3793                     /* Suppress following prompt */
3794                     if (looking_at(buf, &i, "*% ")) {
3795                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3796                         savingComment = FALSE;
3797                         suppressKibitz = 0;
3798                     }
3799                     next_out = i;
3800                 }
3801                 continue;
3802             }
3803
3804             i++;                /* skip unparsed character and loop back */
3805         }
3806
3807         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3808 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3809 //          SendToPlayer(&buf[next_out], i - next_out);
3810             started != STARTED_HOLDINGS && leftover_start > next_out) {
3811             SendToPlayer(&buf[next_out], leftover_start - next_out);
3812             next_out = i;
3813         }
3814
3815         leftover_len = buf_len - leftover_start;
3816         /* if buffer ends with something we couldn't parse,
3817            reparse it after appending the next read */
3818
3819     } else if (count == 0) {
3820         RemoveInputSource(isr);
3821         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3822     } else {
3823         DisplayFatalError(_("Error reading from ICS"), error, 1);
3824     }
3825 }
3826
3827
3828 /* Board style 12 looks like this:
3829
3830    <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
3831
3832  * The "<12> " is stripped before it gets to this routine.  The two
3833  * trailing 0's (flip state and clock ticking) are later addition, and
3834  * some chess servers may not have them, or may have only the first.
3835  * Additional trailing fields may be added in the future.
3836  */
3837
3838 #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"
3839
3840 #define RELATION_OBSERVING_PLAYED    0
3841 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3842 #define RELATION_PLAYING_MYMOVE      1
3843 #define RELATION_PLAYING_NOTMYMOVE  -1
3844 #define RELATION_EXAMINING           2
3845 #define RELATION_ISOLATED_BOARD     -3
3846 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3847
3848 void
3849 ParseBoard12(string)
3850      char *string;
3851 {
3852     GameMode newGameMode;
3853     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3854     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3855     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3856     char to_play, board_chars[200];
3857     char move_str[500], str[500], elapsed_time[500];
3858     char black[32], white[32];
3859     Board board;
3860     int prevMove = currentMove;
3861     int ticking = 2;
3862     ChessMove moveType;
3863     int fromX, fromY, toX, toY;
3864     char promoChar;
3865     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3866     char *bookHit = NULL; // [HGM] book
3867     Boolean weird = FALSE, reqFlag = FALSE;
3868
3869     fromX = fromY = toX = toY = -1;
3870
3871     newGame = FALSE;
3872
3873     if (appData.debugMode)
3874       fprintf(debugFP, _("Parsing board: %s\n"), string);
3875
3876     move_str[0] = NULLCHAR;
3877     elapsed_time[0] = NULLCHAR;
3878     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3879         int  i = 0, j;
3880         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3881             if(string[i] == ' ') { ranks++; files = 0; }
3882             else files++;
3883             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3884             i++;
3885         }
3886         for(j = 0; j <i; j++) board_chars[j] = string[j];
3887         board_chars[i] = '\0';
3888         string += i + 1;
3889     }
3890     n = sscanf(string, PATTERN, &to_play, &double_push,
3891                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3892                &gamenum, white, black, &relation, &basetime, &increment,
3893                &white_stren, &black_stren, &white_time, &black_time,
3894                &moveNum, str, elapsed_time, move_str, &ics_flip,
3895                &ticking);
3896
3897     if (n < 21) {
3898         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3899         DisplayError(str, 0);
3900         return;
3901     }
3902
3903     /* Convert the move number to internal form */
3904     moveNum = (moveNum - 1) * 2;
3905     if (to_play == 'B') moveNum++;
3906     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3907       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3908                         0, 1);
3909       return;
3910     }
3911
3912     switch (relation) {
3913       case RELATION_OBSERVING_PLAYED:
3914       case RELATION_OBSERVING_STATIC:
3915         if (gamenum == -1) {
3916             /* Old ICC buglet */
3917             relation = RELATION_OBSERVING_STATIC;
3918         }
3919         newGameMode = IcsObserving;
3920         break;
3921       case RELATION_PLAYING_MYMOVE:
3922       case RELATION_PLAYING_NOTMYMOVE:
3923         newGameMode =
3924           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3925             IcsPlayingWhite : IcsPlayingBlack;
3926         break;
3927       case RELATION_EXAMINING:
3928         newGameMode = IcsExamining;
3929         break;
3930       case RELATION_ISOLATED_BOARD:
3931       default:
3932         /* Just display this board.  If user was doing something else,
3933            we will forget about it until the next board comes. */
3934         newGameMode = IcsIdle;
3935         break;
3936       case RELATION_STARTING_POSITION:
3937         newGameMode = gameMode;
3938         break;
3939     }
3940
3941     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3942          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3943       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3944       char *toSqr;
3945       for (k = 0; k < ranks; k++) {
3946         for (j = 0; j < files; j++)
3947           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3948         if(gameInfo.holdingsWidth > 1) {
3949              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3950              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3951         }
3952       }
3953       CopyBoard(partnerBoard, board);
3954       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3955         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3956         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3957       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3958       if(toSqr = strchr(str, '-')) {
3959         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3960         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3961       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3962       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3963       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3964       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3965       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3966       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3967                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3968       DisplayMessage(partnerStatus, "");
3969         partnerBoardValid = TRUE;
3970       return;
3971     }
3972
3973     /* Modify behavior for initial board display on move listing
3974        of wild games.
3975        */
3976     switch (ics_getting_history) {
3977       case H_FALSE:
3978       case H_REQUESTED:
3979         break;
3980       case H_GOT_REQ_HEADER:
3981       case H_GOT_UNREQ_HEADER:
3982         /* This is the initial position of the current game */
3983         gamenum = ics_gamenum;
3984         moveNum = 0;            /* old ICS bug workaround */
3985         if (to_play == 'B') {
3986           startedFromSetupPosition = TRUE;
3987           blackPlaysFirst = TRUE;
3988           moveNum = 1;
3989           if (forwardMostMove == 0) forwardMostMove = 1;
3990           if (backwardMostMove == 0) backwardMostMove = 1;
3991           if (currentMove == 0) currentMove = 1;
3992         }
3993         newGameMode = gameMode;
3994         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3995         break;
3996       case H_GOT_UNWANTED_HEADER:
3997         /* This is an initial board that we don't want */
3998         return;
3999       case H_GETTING_MOVES:
4000         /* Should not happen */
4001         DisplayError(_("Error gathering move list: extra board"), 0);
4002         ics_getting_history = H_FALSE;
4003         return;
4004     }
4005
4006    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4007                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4008      /* [HGM] We seem to have switched variant unexpectedly
4009       * Try to guess new variant from board size
4010       */
4011           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4012           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4013           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4014           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4015           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4016           if(!weird) newVariant = VariantNormal;
4017           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4018           /* Get a move list just to see the header, which
4019              will tell us whether this is really bug or zh */
4020           if (ics_getting_history == H_FALSE) {
4021             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4022             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4023             SendToICS(str);
4024           }
4025     }
4026
4027     /* Take action if this is the first board of a new game, or of a
4028        different game than is currently being displayed.  */
4029     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4030         relation == RELATION_ISOLATED_BOARD) {
4031
4032         /* Forget the old game and get the history (if any) of the new one */
4033         if (gameMode != BeginningOfGame) {
4034           Reset(TRUE, TRUE);
4035         }
4036         newGame = TRUE;
4037         if (appData.autoRaiseBoard) BoardToTop();
4038         prevMove = -3;
4039         if (gamenum == -1) {
4040             newGameMode = IcsIdle;
4041         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4042                    appData.getMoveList && !reqFlag) {
4043             /* Need to get game history */
4044             ics_getting_history = H_REQUESTED;
4045             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4046             SendToICS(str);
4047         }
4048
4049         /* Initially flip the board to have black on the bottom if playing
4050            black or if the ICS flip flag is set, but let the user change
4051            it with the Flip View button. */
4052         flipView = appData.autoFlipView ?
4053           (newGameMode == IcsPlayingBlack) || ics_flip :
4054           appData.flipView;
4055
4056         /* Done with values from previous mode; copy in new ones */
4057         gameMode = newGameMode;
4058         ModeHighlight();
4059         ics_gamenum = gamenum;
4060         if (gamenum == gs_gamenum) {
4061             int klen = strlen(gs_kind);
4062             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4063             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4064             gameInfo.event = StrSave(str);
4065         } else {
4066             gameInfo.event = StrSave("ICS game");
4067         }
4068         gameInfo.site = StrSave(appData.icsHost);
4069         gameInfo.date = PGNDate();
4070         gameInfo.round = StrSave("-");
4071         gameInfo.white = StrSave(white);
4072         gameInfo.black = StrSave(black);
4073         timeControl = basetime * 60 * 1000;
4074         timeControl_2 = 0;
4075         timeIncrement = increment * 1000;
4076         movesPerSession = 0;
4077         gameInfo.timeControl = TimeControlTagValue();
4078         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4079   if (appData.debugMode) {
4080     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4081     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4082     setbuf(debugFP, NULL);
4083   }
4084
4085         gameInfo.outOfBook = NULL;
4086
4087         /* Do we have the ratings? */
4088         if (strcmp(player1Name, white) == 0 &&
4089             strcmp(player2Name, black) == 0) {
4090             if (appData.debugMode)
4091               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4092                       player1Rating, player2Rating);
4093             gameInfo.whiteRating = player1Rating;
4094             gameInfo.blackRating = player2Rating;
4095         } else if (strcmp(player2Name, white) == 0 &&
4096                    strcmp(player1Name, black) == 0) {
4097             if (appData.debugMode)
4098               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4099                       player2Rating, player1Rating);
4100             gameInfo.whiteRating = player2Rating;
4101             gameInfo.blackRating = player1Rating;
4102         }
4103         player1Name[0] = player2Name[0] = NULLCHAR;
4104
4105         /* Silence shouts if requested */
4106         if (appData.quietPlay &&
4107             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4108             SendToICS(ics_prefix);
4109             SendToICS("set shout 0\n");
4110         }
4111     }
4112
4113     /* Deal with midgame name changes */
4114     if (!newGame) {
4115         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4116             if (gameInfo.white) free(gameInfo.white);
4117             gameInfo.white = StrSave(white);
4118         }
4119         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4120             if (gameInfo.black) free(gameInfo.black);
4121             gameInfo.black = StrSave(black);
4122         }
4123     }
4124
4125     /* Throw away game result if anything actually changes in examine mode */
4126     if (gameMode == IcsExamining && !newGame) {
4127         gameInfo.result = GameUnfinished;
4128         if (gameInfo.resultDetails != NULL) {
4129             free(gameInfo.resultDetails);
4130             gameInfo.resultDetails = NULL;
4131         }
4132     }
4133
4134     /* In pausing && IcsExamining mode, we ignore boards coming
4135        in if they are in a different variation than we are. */
4136     if (pauseExamInvalid) return;
4137     if (pausing && gameMode == IcsExamining) {
4138         if (moveNum <= pauseExamForwardMostMove) {
4139             pauseExamInvalid = TRUE;
4140             forwardMostMove = pauseExamForwardMostMove;
4141             return;
4142         }
4143     }
4144
4145   if (appData.debugMode) {
4146     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4147   }
4148     /* Parse the board */
4149     for (k = 0; k < ranks; k++) {
4150       for (j = 0; j < files; j++)
4151         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4152       if(gameInfo.holdingsWidth > 1) {
4153            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4154            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4155       }
4156     }
4157     CopyBoard(boards[moveNum], board);
4158     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4159     if (moveNum == 0) {
4160         startedFromSetupPosition =
4161           !CompareBoards(board, initialPosition);
4162         if(startedFromSetupPosition)
4163             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4164     }
4165
4166     /* [HGM] Set castling rights. Take the outermost Rooks,
4167        to make it also work for FRC opening positions. Note that board12
4168        is really defective for later FRC positions, as it has no way to
4169        indicate which Rook can castle if they are on the same side of King.
4170        For the initial position we grant rights to the outermost Rooks,
4171        and remember thos rights, and we then copy them on positions
4172        later in an FRC game. This means WB might not recognize castlings with
4173        Rooks that have moved back to their original position as illegal,
4174        but in ICS mode that is not its job anyway.
4175     */
4176     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4177     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4178
4179         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4180             if(board[0][i] == WhiteRook) j = i;
4181         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4183             if(board[0][i] == WhiteRook) j = i;
4184         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4185         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4186             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4187         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4188         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4189             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4190         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4191
4192         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4193         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4194             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4195         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4196             if(board[BOARD_HEIGHT-1][k] == bKing)
4197                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4198         if(gameInfo.variant == VariantTwoKings) {
4199             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4200             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4201             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4202         }
4203     } else { int r;
4204         r = boards[moveNum][CASTLING][0] = initialRights[0];
4205         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4206         r = boards[moveNum][CASTLING][1] = initialRights[1];
4207         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4208         r = boards[moveNum][CASTLING][3] = initialRights[3];
4209         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4210         r = boards[moveNum][CASTLING][4] = initialRights[4];
4211         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4212         /* wildcastle kludge: always assume King has rights */
4213         r = boards[moveNum][CASTLING][2] = initialRights[2];
4214         r = boards[moveNum][CASTLING][5] = initialRights[5];
4215     }
4216     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4217     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4218
4219
4220     if (ics_getting_history == H_GOT_REQ_HEADER ||
4221         ics_getting_history == H_GOT_UNREQ_HEADER) {
4222         /* This was an initial position from a move list, not
4223            the current position */
4224         return;
4225     }
4226
4227     /* Update currentMove and known move number limits */
4228     newMove = newGame || moveNum > forwardMostMove;
4229
4230     if (newGame) {
4231         forwardMostMove = backwardMostMove = currentMove = moveNum;
4232         if (gameMode == IcsExamining && moveNum == 0) {
4233           /* Workaround for ICS limitation: we are not told the wild
4234              type when starting to examine a game.  But if we ask for
4235              the move list, the move list header will tell us */
4236             ics_getting_history = H_REQUESTED;
4237             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4238             SendToICS(str);
4239         }
4240     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4241                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4242 #if ZIPPY
4243         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4244         /* [HGM] applied this also to an engine that is silently watching        */
4245         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4246             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4247             gameInfo.variant == currentlyInitializedVariant) {
4248           takeback = forwardMostMove - moveNum;
4249           for (i = 0; i < takeback; i++) {
4250             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4251             SendToProgram("undo\n", &first);
4252           }
4253         }
4254 #endif
4255
4256         forwardMostMove = moveNum;
4257         if (!pausing || currentMove > forwardMostMove)
4258           currentMove = forwardMostMove;
4259     } else {
4260         /* New part of history that is not contiguous with old part */
4261         if (pausing && gameMode == IcsExamining) {
4262             pauseExamInvalid = TRUE;
4263             forwardMostMove = pauseExamForwardMostMove;
4264             return;
4265         }
4266         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4267 #if ZIPPY
4268             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4269                 // [HGM] when we will receive the move list we now request, it will be
4270                 // fed to the engine from the first move on. So if the engine is not
4271                 // in the initial position now, bring it there.
4272                 InitChessProgram(&first, 0);
4273             }
4274 #endif
4275             ics_getting_history = H_REQUESTED;
4276             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4277             SendToICS(str);
4278         }
4279         forwardMostMove = backwardMostMove = currentMove = moveNum;
4280     }
4281
4282     /* Update the clocks */
4283     if (strchr(elapsed_time, '.')) {
4284       /* Time is in ms */
4285       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4286       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4287     } else {
4288       /* Time is in seconds */
4289       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4290       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4291     }
4292
4293
4294 #if ZIPPY
4295     if (appData.zippyPlay && newGame &&
4296         gameMode != IcsObserving && gameMode != IcsIdle &&
4297         gameMode != IcsExamining)
4298       ZippyFirstBoard(moveNum, basetime, increment);
4299 #endif
4300
4301     /* Put the move on the move list, first converting
4302        to canonical algebraic form. */
4303     if (moveNum > 0) {
4304   if (appData.debugMode) {
4305     if (appData.debugMode) { int f = forwardMostMove;
4306         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4307                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4308                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4309     }
4310     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4311     fprintf(debugFP, "moveNum = %d\n", moveNum);
4312     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4313     setbuf(debugFP, NULL);
4314   }
4315         if (moveNum <= backwardMostMove) {
4316             /* We don't know what the board looked like before
4317                this move.  Punt. */
4318           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4319             strcat(parseList[moveNum - 1], " ");
4320             strcat(parseList[moveNum - 1], elapsed_time);
4321             moveList[moveNum - 1][0] = NULLCHAR;
4322         } else if (strcmp(move_str, "none") == 0) {
4323             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4324             /* Again, we don't know what the board looked like;
4325                this is really the start of the game. */
4326             parseList[moveNum - 1][0] = NULLCHAR;
4327             moveList[moveNum - 1][0] = NULLCHAR;
4328             backwardMostMove = moveNum;
4329             startedFromSetupPosition = TRUE;
4330             fromX = fromY = toX = toY = -1;
4331         } else {
4332           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4333           //                 So we parse the long-algebraic move string in stead of the SAN move
4334           int valid; char buf[MSG_SIZ], *prom;
4335
4336           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4337                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4338           // str looks something like "Q/a1-a2"; kill the slash
4339           if(str[1] == '/')
4340             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4341           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4342           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4343                 strcat(buf, prom); // long move lacks promo specification!
4344           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4345                 if(appData.debugMode)
4346                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4347                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4348           }
4349           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4350                                 &fromX, &fromY, &toX, &toY, &promoChar)
4351                || ParseOneMove(buf, moveNum - 1, &moveType,
4352                                 &fromX, &fromY, &toX, &toY, &promoChar);
4353           // end of long SAN patch
4354           if (valid) {
4355             (void) CoordsToAlgebraic(boards[moveNum - 1],
4356                                      PosFlags(moveNum - 1),
4357                                      fromY, fromX, toY, toX, promoChar,
4358                                      parseList[moveNum-1]);
4359             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4360               case MT_NONE:
4361               case MT_STALEMATE:
4362               default:
4363                 break;
4364               case MT_CHECK:
4365                 if(gameInfo.variant != VariantShogi)
4366                     strcat(parseList[moveNum - 1], "+");
4367                 break;
4368               case MT_CHECKMATE:
4369               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4370                 strcat(parseList[moveNum - 1], "#");
4371                 break;
4372             }
4373             strcat(parseList[moveNum - 1], " ");
4374             strcat(parseList[moveNum - 1], elapsed_time);
4375             /* currentMoveString is set as a side-effect of ParseOneMove */
4376             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4377             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4378             strcat(moveList[moveNum - 1], "\n");
4379
4380             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4381               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4382                 ChessSquare old, new = boards[moveNum][k][j];
4383                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4384                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4385                   if(old == new) continue;
4386                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4387                   else if(new == WhiteWazir || new == BlackWazir) {
4388                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4389                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4390                       else boards[moveNum][k][j] = old; // preserve type of Gold
4391                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4392                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4393               }
4394           } else {
4395             /* Move from ICS was illegal!?  Punt. */
4396             if (appData.debugMode) {
4397               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4398               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4399             }
4400             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4401             strcat(parseList[moveNum - 1], " ");
4402             strcat(parseList[moveNum - 1], elapsed_time);
4403             moveList[moveNum - 1][0] = NULLCHAR;
4404             fromX = fromY = toX = toY = -1;
4405           }
4406         }
4407   if (appData.debugMode) {
4408     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4409     setbuf(debugFP, NULL);
4410   }
4411
4412 #if ZIPPY
4413         /* Send move to chess program (BEFORE animating it). */
4414         if (appData.zippyPlay && !newGame && newMove &&
4415            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4416
4417             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4418                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4419                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4420                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4421                             move_str);
4422                     DisplayError(str, 0);
4423                 } else {
4424                     if (first.sendTime) {
4425                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4426                     }
4427                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4428                     if (firstMove && !bookHit) {
4429                         firstMove = FALSE;
4430                         if (first.useColors) {
4431                           SendToProgram(gameMode == IcsPlayingWhite ?
4432                                         "white\ngo\n" :
4433                                         "black\ngo\n", &first);
4434                         } else {
4435                           SendToProgram("go\n", &first);
4436                         }
4437                         first.maybeThinking = TRUE;
4438                     }
4439                 }
4440             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4441               if (moveList[moveNum - 1][0] == NULLCHAR) {
4442                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4443                 DisplayError(str, 0);
4444               } else {
4445                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4446                 SendMoveToProgram(moveNum - 1, &first);
4447               }
4448             }
4449         }
4450 #endif
4451     }
4452
4453     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4454         /* If move comes from a remote source, animate it.  If it
4455            isn't remote, it will have already been animated. */
4456         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4457             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4458         }
4459         if (!pausing && appData.highlightLastMove) {
4460             SetHighlights(fromX, fromY, toX, toY);
4461         }
4462     }
4463
4464     /* Start the clocks */
4465     whiteFlag = blackFlag = FALSE;
4466     appData.clockMode = !(basetime == 0 && increment == 0);
4467     if (ticking == 0) {
4468       ics_clock_paused = TRUE;
4469       StopClocks();
4470     } else if (ticking == 1) {
4471       ics_clock_paused = FALSE;
4472     }
4473     if (gameMode == IcsIdle ||
4474         relation == RELATION_OBSERVING_STATIC ||
4475         relation == RELATION_EXAMINING ||
4476         ics_clock_paused)
4477       DisplayBothClocks();
4478     else
4479       StartClocks();
4480
4481     /* Display opponents and material strengths */
4482     if (gameInfo.variant != VariantBughouse &&
4483         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4484         if (tinyLayout || smallLayout) {
4485             if(gameInfo.variant == VariantNormal)
4486               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4487                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4488                     basetime, increment);
4489             else
4490               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment, (int) gameInfo.variant);
4493         } else {
4494             if(gameInfo.variant == VariantNormal)
4495               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4496                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4497                     basetime, increment);
4498             else
4499               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4500                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4501                     basetime, increment, VariantName(gameInfo.variant));
4502         }
4503         DisplayTitle(str);
4504   if (appData.debugMode) {
4505     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4506   }
4507     }
4508
4509
4510     /* Display the board */
4511     if (!pausing && !appData.noGUI) {
4512
4513       if (appData.premove)
4514           if (!gotPremove ||
4515              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4516              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4517               ClearPremoveHighlights();
4518
4519       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4520         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4521       DrawPosition(j, boards[currentMove]);
4522
4523       DisplayMove(moveNum - 1);
4524       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4525             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4526               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4527         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4528       }
4529     }
4530
4531     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4532 #if ZIPPY
4533     if(bookHit) { // [HGM] book: simulate book reply
4534         static char bookMove[MSG_SIZ]; // a bit generous?
4535
4536         programStats.nodes = programStats.depth = programStats.time =
4537         programStats.score = programStats.got_only_move = 0;
4538         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4539
4540         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4541         strcat(bookMove, bookHit);
4542         HandleMachineMove(bookMove, &first);
4543     }
4544 #endif
4545 }
4546
4547 void
4548 GetMoveListEvent()
4549 {
4550     char buf[MSG_SIZ];
4551     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4552         ics_getting_history = H_REQUESTED;
4553         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4554         SendToICS(buf);
4555     }
4556 }
4557
4558 void
4559 AnalysisPeriodicEvent(force)
4560      int force;
4561 {
4562     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4563          && !force) || !appData.periodicUpdates)
4564       return;
4565
4566     /* Send . command to Crafty to collect stats */
4567     SendToProgram(".\n", &first);
4568
4569     /* Don't send another until we get a response (this makes
4570        us stop sending to old Crafty's which don't understand
4571        the "." command (sending illegal cmds resets node count & time,
4572        which looks bad)) */
4573     programStats.ok_to_send = 0;
4574 }
4575
4576 void ics_update_width(new_width)
4577         int new_width;
4578 {
4579         ics_printf("set width %d\n", new_width);
4580 }
4581
4582 void
4583 SendMoveToProgram(moveNum, cps)
4584      int moveNum;
4585      ChessProgramState *cps;
4586 {
4587     char buf[MSG_SIZ];
4588
4589     if (cps->useUsermove) {
4590       SendToProgram("usermove ", cps);
4591     }
4592     if (cps->useSAN) {
4593       char *space;
4594       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4595         int len = space - parseList[moveNum];
4596         memcpy(buf, parseList[moveNum], len);
4597         buf[len++] = '\n';
4598         buf[len] = NULLCHAR;
4599       } else {
4600         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4601       }
4602       SendToProgram(buf, cps);
4603     } else {
4604       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4605         AlphaRank(moveList[moveNum], 4);
4606         SendToProgram(moveList[moveNum], cps);
4607         AlphaRank(moveList[moveNum], 4); // and back
4608       } else
4609       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4610        * the engine. It would be nice to have a better way to identify castle
4611        * moves here. */
4612       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4613                                                                          && cps->useOOCastle) {
4614         int fromX = moveList[moveNum][0] - AAA;
4615         int fromY = moveList[moveNum][1] - ONE;
4616         int toX = moveList[moveNum][2] - AAA;
4617         int toY = moveList[moveNum][3] - ONE;
4618         if((boards[moveNum][fromY][fromX] == WhiteKing
4619             && boards[moveNum][toY][toX] == WhiteRook)
4620            || (boards[moveNum][fromY][fromX] == BlackKing
4621                && boards[moveNum][toY][toX] == BlackRook)) {
4622           if(toX > fromX) SendToProgram("O-O\n", cps);
4623           else SendToProgram("O-O-O\n", cps);
4624         }
4625         else SendToProgram(moveList[moveNum], cps);
4626       }
4627       else SendToProgram(moveList[moveNum], cps);
4628       /* End of additions by Tord */
4629     }
4630
4631     /* [HGM] setting up the opening has brought engine in force mode! */
4632     /*       Send 'go' if we are in a mode where machine should play. */
4633     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4634         (gameMode == TwoMachinesPlay   ||
4635 #if ZIPPY
4636          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4637 #endif
4638          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4639         SendToProgram("go\n", cps);
4640   if (appData.debugMode) {
4641     fprintf(debugFP, "(extra)\n");
4642   }
4643     }
4644     setboardSpoiledMachineBlack = 0;
4645 }
4646
4647 void
4648 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4649      ChessMove moveType;
4650      int fromX, fromY, toX, toY;
4651      char promoChar;
4652 {
4653     char user_move[MSG_SIZ];
4654
4655     switch (moveType) {
4656       default:
4657         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4658                 (int)moveType, fromX, fromY, toX, toY);
4659         DisplayError(user_move + strlen("say "), 0);
4660         break;
4661       case WhiteKingSideCastle:
4662       case BlackKingSideCastle:
4663       case WhiteQueenSideCastleWild:
4664       case BlackQueenSideCastleWild:
4665       /* PUSH Fabien */
4666       case WhiteHSideCastleFR:
4667       case BlackHSideCastleFR:
4668       /* POP Fabien */
4669         snprintf(user_move, MSG_SIZ, "o-o\n");
4670         break;
4671       case WhiteQueenSideCastle:
4672       case BlackQueenSideCastle:
4673       case WhiteKingSideCastleWild:
4674       case BlackKingSideCastleWild:
4675       /* PUSH Fabien */
4676       case WhiteASideCastleFR:
4677       case BlackASideCastleFR:
4678       /* POP Fabien */
4679         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4680         break;
4681       case WhiteNonPromotion:
4682       case BlackNonPromotion:
4683         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4684         break;
4685       case WhitePromotion:
4686       case BlackPromotion:
4687         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4688           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4689                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4690                 PieceToChar(WhiteFerz));
4691         else if(gameInfo.variant == VariantGreat)
4692           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4693                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4694                 PieceToChar(WhiteMan));
4695         else
4696           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4697                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4698                 promoChar);
4699         break;
4700       case WhiteDrop:
4701       case BlackDrop:
4702       drop:
4703         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4704                  ToUpper(PieceToChar((ChessSquare) fromX)),
4705                  AAA + toX, ONE + toY);
4706         break;
4707       case IllegalMove:  /* could be a variant we don't quite understand */
4708         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4709       case NormalMove:
4710       case WhiteCapturesEnPassant:
4711       case BlackCapturesEnPassant:
4712         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4713                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4714         break;
4715     }
4716     SendToICS(user_move);
4717     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4718         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4719 }
4720
4721 void
4722 UploadGameEvent()
4723 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4724     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4725     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4726     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4727         DisplayError("You cannot do this while you are playing or observing", 0);
4728         return;
4729     }
4730     if(gameMode != IcsExamining) { // is this ever not the case?
4731         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4732
4733         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4734           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4735         } else { // on FICS we must first go to general examine mode
4736           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4737         }
4738         if(gameInfo.variant != VariantNormal) {
4739             // try figure out wild number, as xboard names are not always valid on ICS
4740             for(i=1; i<=36; i++) {
4741               snprintf(buf, MSG_SIZ, "wild/%d", i);
4742                 if(StringToVariant(buf) == gameInfo.variant) break;
4743             }
4744             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4745             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4746             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4747         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4748         SendToICS(ics_prefix);
4749         SendToICS(buf);
4750         if(startedFromSetupPosition || backwardMostMove != 0) {
4751           fen = PositionToFEN(backwardMostMove, NULL);
4752           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4753             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4754             SendToICS(buf);
4755           } else { // FICS: everything has to set by separate bsetup commands
4756             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4757             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4758             SendToICS(buf);
4759             if(!WhiteOnMove(backwardMostMove)) {
4760                 SendToICS("bsetup tomove black\n");
4761             }
4762             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4763             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4764             SendToICS(buf);
4765             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4766             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4767             SendToICS(buf);
4768             i = boards[backwardMostMove][EP_STATUS];
4769             if(i >= 0) { // set e.p.
4770               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4771                 SendToICS(buf);
4772             }
4773             bsetup++;
4774           }
4775         }
4776       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4777             SendToICS("bsetup done\n"); // switch to normal examining.
4778     }
4779     for(i = backwardMostMove; i<last; i++) {
4780         char buf[20];
4781         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4782         SendToICS(buf);
4783     }
4784     SendToICS(ics_prefix);
4785     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4786 }
4787
4788 void
4789 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4790      int rf, ff, rt, ft;
4791      char promoChar;
4792      char move[7];
4793 {
4794     if (rf == DROP_RANK) {
4795       sprintf(move, "%c@%c%c\n",
4796                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4797     } else {
4798         if (promoChar == 'x' || promoChar == NULLCHAR) {
4799           sprintf(move, "%c%c%c%c\n",
4800                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4801         } else {
4802             sprintf(move, "%c%c%c%c%c\n",
4803                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4804         }
4805     }
4806 }
4807
4808 void
4809 ProcessICSInitScript(f)
4810      FILE *f;
4811 {
4812     char buf[MSG_SIZ];
4813
4814     while (fgets(buf, MSG_SIZ, f)) {
4815         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4816     }
4817
4818     fclose(f);
4819 }
4820
4821
4822 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4823 void
4824 AlphaRank(char *move, int n)
4825 {
4826 //    char *p = move, c; int x, y;
4827
4828     if (appData.debugMode) {
4829         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4830     }
4831
4832     if(move[1]=='*' &&
4833        move[2]>='0' && move[2]<='9' &&
4834        move[3]>='a' && move[3]<='x'    ) {
4835         move[1] = '@';
4836         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4837         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4838     } else
4839     if(move[0]>='0' && move[0]<='9' &&
4840        move[1]>='a' && move[1]<='x' &&
4841        move[2]>='0' && move[2]<='9' &&
4842        move[3]>='a' && move[3]<='x'    ) {
4843         /* input move, Shogi -> normal */
4844         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4845         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4846         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4847         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4848     } else
4849     if(move[1]=='@' &&
4850        move[3]>='0' && move[3]<='9' &&
4851        move[2]>='a' && move[2]<='x'    ) {
4852         move[1] = '*';
4853         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4854         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4855     } else
4856     if(
4857        move[0]>='a' && move[0]<='x' &&
4858        move[3]>='0' && move[3]<='9' &&
4859        move[2]>='a' && move[2]<='x'    ) {
4860          /* output move, normal -> Shogi */
4861         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4862         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4863         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4864         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4865         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4866     }
4867     if (appData.debugMode) {
4868         fprintf(debugFP, "   out = '%s'\n", move);
4869     }
4870 }
4871
4872 char yy_textstr[8000];
4873
4874 /* Parser for moves from gnuchess, ICS, or user typein box */
4875 Boolean
4876 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4877      char *move;
4878      int moveNum;
4879      ChessMove *moveType;
4880      int *fromX, *fromY, *toX, *toY;
4881      char *promoChar;
4882 {
4883     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4884
4885     switch (*moveType) {
4886       case WhitePromotion:
4887       case BlackPromotion:
4888       case WhiteNonPromotion:
4889       case BlackNonPromotion:
4890       case NormalMove:
4891       case WhiteCapturesEnPassant:
4892       case BlackCapturesEnPassant:
4893       case WhiteKingSideCastle:
4894       case WhiteQueenSideCastle:
4895       case BlackKingSideCastle:
4896       case BlackQueenSideCastle:
4897       case WhiteKingSideCastleWild:
4898       case WhiteQueenSideCastleWild:
4899       case BlackKingSideCastleWild:
4900       case BlackQueenSideCastleWild:
4901       /* Code added by Tord: */
4902       case WhiteHSideCastleFR:
4903       case WhiteASideCastleFR:
4904       case BlackHSideCastleFR:
4905       case BlackASideCastleFR:
4906       /* End of code added by Tord */
4907       case IllegalMove:         /* bug or odd chess variant */
4908         *fromX = currentMoveString[0] - AAA;
4909         *fromY = currentMoveString[1] - ONE;
4910         *toX = currentMoveString[2] - AAA;
4911         *toY = currentMoveString[3] - ONE;
4912         *promoChar = currentMoveString[4];
4913         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4914             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4915     if (appData.debugMode) {
4916         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4917     }
4918             *fromX = *fromY = *toX = *toY = 0;
4919             return FALSE;
4920         }
4921         if (appData.testLegality) {
4922           return (*moveType != IllegalMove);
4923         } else {
4924           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4925                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4926         }
4927
4928       case WhiteDrop:
4929       case BlackDrop:
4930         *fromX = *moveType == WhiteDrop ?
4931           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4932           (int) CharToPiece(ToLower(currentMoveString[0]));
4933         *fromY = DROP_RANK;
4934         *toX = currentMoveString[2] - AAA;
4935         *toY = currentMoveString[3] - ONE;
4936         *promoChar = NULLCHAR;
4937         return TRUE;
4938
4939       case AmbiguousMove:
4940       case ImpossibleMove:
4941       case EndOfFile:
4942       case ElapsedTime:
4943       case Comment:
4944       case PGNTag:
4945       case NAG:
4946       case WhiteWins:
4947       case BlackWins:
4948       case GameIsDrawn:
4949       default:
4950     if (appData.debugMode) {
4951         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4952     }
4953         /* bug? */
4954         *fromX = *fromY = *toX = *toY = 0;
4955         *promoChar = NULLCHAR;
4956         return FALSE;
4957     }
4958 }
4959
4960
4961 void
4962 ParsePV(char *pv, Boolean storeComments)
4963 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4964   int fromX, fromY, toX, toY; char promoChar;
4965   ChessMove moveType;
4966   Boolean valid;
4967   int nr = 0;
4968
4969   endPV = forwardMostMove;
4970   do {
4971     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4972     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4973     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4974 if(appData.debugMode){
4975 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);
4976 }
4977     if(!valid && nr == 0 &&
4978        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4979         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4980         // Hande case where played move is different from leading PV move
4981         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4982         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4983         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4984         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4985           endPV += 2; // if position different, keep this
4986           moveList[endPV-1][0] = fromX + AAA;
4987           moveList[endPV-1][1] = fromY + ONE;
4988           moveList[endPV-1][2] = toX + AAA;
4989           moveList[endPV-1][3] = toY + ONE;
4990           parseList[endPV-1][0] = NULLCHAR;
4991           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4992         }
4993       }
4994     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4995     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4996     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4997     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4998         valid++; // allow comments in PV
4999         continue;
5000     }
5001     nr++;
5002     if(endPV+1 > framePtr) break; // no space, truncate
5003     if(!valid) break;
5004     endPV++;
5005     CopyBoard(boards[endPV], boards[endPV-1]);
5006     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5007     moveList[endPV-1][0] = fromX + AAA;
5008     moveList[endPV-1][1] = fromY + ONE;
5009     moveList[endPV-1][2] = toX + AAA;
5010     moveList[endPV-1][3] = toY + ONE;
5011     if(storeComments)
5012         CoordsToAlgebraic(boards[endPV - 1],
5013                              PosFlags(endPV - 1),
5014                              fromY, fromX, toY, toX, promoChar,
5015                              parseList[endPV - 1]);
5016     else
5017         parseList[endPV-1][0] = NULLCHAR;
5018   } while(valid);
5019   currentMove = endPV;
5020   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5021   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5022                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5023   DrawPosition(TRUE, boards[currentMove]);
5024 }
5025
5026 static int lastX, lastY;
5027
5028 Boolean
5029 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5030 {
5031         int startPV;
5032         char *p;
5033
5034         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5035         lastX = x; lastY = y;
5036         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5037         startPV = index;
5038         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5039         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5040         index = startPV;
5041         do{ while(buf[index] && buf[index] != '\n') index++;
5042         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5043         buf[index] = 0;
5044         ParsePV(buf+startPV, FALSE);
5045         *start = startPV; *end = index-1;
5046         return TRUE;
5047 }
5048
5049 Boolean
5050 LoadPV(int x, int y)
5051 { // called on right mouse click to load PV
5052   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5053   lastX = x; lastY = y;
5054   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5055   return TRUE;
5056 }
5057
5058 void
5059 UnLoadPV()
5060 {
5061   if(endPV < 0) return;
5062   endPV = -1;
5063   currentMove = forwardMostMove;
5064   ClearPremoveHighlights();
5065   DrawPosition(TRUE, boards[currentMove]);
5066 }
5067
5068 void
5069 MovePV(int x, int y, int h)
5070 { // step through PV based on mouse coordinates (called on mouse move)
5071   int margin = h>>3, step = 0;
5072
5073   if(endPV < 0) return;
5074   // we must somehow check if right button is still down (might be released off board!)
5075   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5076   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5077   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5078   if(!step) return;
5079   lastX = x; lastY = y;
5080   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5081   currentMove += step;
5082   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5083   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5084                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5085   DrawPosition(FALSE, boards[currentMove]);
5086 }
5087
5088
5089 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5090 // All positions will have equal probability, but the current method will not provide a unique
5091 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5092 #define DARK 1
5093 #define LITE 2
5094 #define ANY 3
5095
5096 int squaresLeft[4];
5097 int piecesLeft[(int)BlackPawn];
5098 int seed, nrOfShuffles;
5099
5100 void GetPositionNumber()
5101 {       // sets global variable seed
5102         int i;
5103
5104         seed = appData.defaultFrcPosition;
5105         if(seed < 0) { // randomize based on time for negative FRC position numbers
5106                 for(i=0; i<50; i++) seed += random();
5107                 seed = random() ^ random() >> 8 ^ random() << 8;
5108                 if(seed<0) seed = -seed;
5109         }
5110 }
5111
5112 int put(Board board, int pieceType, int rank, int n, int shade)
5113 // put the piece on the (n-1)-th empty squares of the given shade
5114 {
5115         int i;
5116
5117         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5118                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5119                         board[rank][i] = (ChessSquare) pieceType;
5120                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5121                         squaresLeft[ANY]--;
5122                         piecesLeft[pieceType]--;
5123                         return i;
5124                 }
5125         }
5126         return -1;
5127 }
5128
5129
5130 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5131 // calculate where the next piece goes, (any empty square), and put it there
5132 {
5133         int i;
5134
5135         i = seed % squaresLeft[shade];
5136         nrOfShuffles *= squaresLeft[shade];
5137         seed /= squaresLeft[shade];
5138         put(board, pieceType, rank, i, shade);
5139 }
5140
5141 void AddTwoPieces(Board board, int pieceType, int rank)
5142 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5143 {
5144         int i, n=squaresLeft[ANY], j=n-1, k;
5145
5146         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5147         i = seed % k;  // pick one
5148         nrOfShuffles *= k;
5149         seed /= k;
5150         while(i >= j) i -= j--;
5151         j = n - 1 - j; i += j;
5152         put(board, pieceType, rank, j, ANY);
5153         put(board, pieceType, rank, i, ANY);
5154 }
5155
5156 void SetUpShuffle(Board board, int number)
5157 {
5158         int i, p, first=1;
5159
5160         GetPositionNumber(); nrOfShuffles = 1;
5161
5162         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5163         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5164         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5165
5166         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5167
5168         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5169             p = (int) board[0][i];
5170             if(p < (int) BlackPawn) piecesLeft[p] ++;
5171             board[0][i] = EmptySquare;
5172         }
5173
5174         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5175             // shuffles restricted to allow normal castling put KRR first
5176             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5177                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5178             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5179                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5180             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5181                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5182             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5183                 put(board, WhiteRook, 0, 0, ANY);
5184             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5185         }
5186
5187         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5188             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5189             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5190                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5191                 while(piecesLeft[p] >= 2) {
5192                     AddOnePiece(board, p, 0, LITE);
5193                     AddOnePiece(board, p, 0, DARK);
5194                 }
5195                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5196             }
5197
5198         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5199             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5200             // but we leave King and Rooks for last, to possibly obey FRC restriction
5201             if(p == (int)WhiteRook) continue;
5202             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5203             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5204         }
5205
5206         // now everything is placed, except perhaps King (Unicorn) and Rooks
5207
5208         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5209             // Last King gets castling rights
5210             while(piecesLeft[(int)WhiteUnicorn]) {
5211                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5212                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5213             }
5214
5215             while(piecesLeft[(int)WhiteKing]) {
5216                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5217                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5218             }
5219
5220
5221         } else {
5222             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5223             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5224         }
5225
5226         // Only Rooks can be left; simply place them all
5227         while(piecesLeft[(int)WhiteRook]) {
5228                 i = put(board, WhiteRook, 0, 0, ANY);
5229                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5230                         if(first) {
5231                                 first=0;
5232                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5233                         }
5234                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5235                 }
5236         }
5237         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5238             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5239         }
5240
5241         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5242 }
5243
5244 int SetCharTable( char *table, const char * map )
5245 /* [HGM] moved here from winboard.c because of its general usefulness */
5246 /*       Basically a safe strcpy that uses the last character as King */
5247 {
5248     int result = FALSE; int NrPieces;
5249
5250     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5251                     && NrPieces >= 12 && !(NrPieces&1)) {
5252         int i; /* [HGM] Accept even length from 12 to 34 */
5253
5254         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5255         for( i=0; i<NrPieces/2-1; i++ ) {
5256             table[i] = map[i];
5257             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5258         }
5259         table[(int) WhiteKing]  = map[NrPieces/2-1];
5260         table[(int) BlackKing]  = map[NrPieces-1];
5261
5262         result = TRUE;
5263     }
5264
5265     return result;
5266 }
5267
5268 void Prelude(Board board)
5269 {       // [HGM] superchess: random selection of exo-pieces
5270         int i, j, k; ChessSquare p;
5271         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5272
5273         GetPositionNumber(); // use FRC position number
5274
5275         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5276             SetCharTable(pieceToChar, appData.pieceToCharTable);
5277             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5278                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5279         }
5280
5281         j = seed%4;                 seed /= 4;
5282         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5283         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5284         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5285         j = seed%3 + (seed%3 >= j); seed /= 3;
5286         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5287         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5288         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5289         j = seed%3;                 seed /= 3;
5290         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5291         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5292         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5293         j = seed%2 + (seed%2 >= j); seed /= 2;
5294         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5295         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5296         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5297         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5298         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5299         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5300         put(board, exoPieces[0],    0, 0, ANY);
5301         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5302 }
5303
5304 void
5305 InitPosition(redraw)
5306      int redraw;
5307 {
5308     ChessSquare (* pieces)[BOARD_FILES];
5309     int i, j, pawnRow, overrule,
5310     oldx = gameInfo.boardWidth,
5311     oldy = gameInfo.boardHeight,
5312     oldh = gameInfo.holdingsWidth,
5313     oldv = gameInfo.variant;
5314
5315     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5316
5317     /* [AS] Initialize pv info list [HGM] and game status */
5318     {
5319         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5320             pvInfoList[i].depth = 0;
5321             boards[i][EP_STATUS] = EP_NONE;
5322             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5323         }
5324
5325         initialRulePlies = 0; /* 50-move counter start */
5326
5327         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5328         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5329     }
5330
5331
5332     /* [HGM] logic here is completely changed. In stead of full positions */
5333     /* the initialized data only consist of the two backranks. The switch */
5334     /* selects which one we will use, which is than copied to the Board   */
5335     /* initialPosition, which for the rest is initialized by Pawns and    */
5336     /* empty squares. This initial position is then copied to boards[0],  */
5337     /* possibly after shuffling, so that it remains available.            */
5338
5339     gameInfo.holdingsWidth = 0; /* default board sizes */
5340     gameInfo.boardWidth    = 8;
5341     gameInfo.boardHeight   = 8;
5342     gameInfo.holdingsSize  = 0;
5343     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5344     for(i=0; i<BOARD_FILES-2; i++)
5345       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5346     initialPosition[EP_STATUS] = EP_NONE;
5347     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5348     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5349          SetCharTable(pieceNickName, appData.pieceNickNames);
5350     else SetCharTable(pieceNickName, "............");
5351
5352     switch (gameInfo.variant) {
5353     case VariantFischeRandom:
5354       shuffleOpenings = TRUE;
5355     default:
5356       pieces = FIDEArray;
5357       break;
5358     case VariantShatranj:
5359       pieces = ShatranjArray;
5360       nrCastlingRights = 0;
5361       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5362       break;
5363     case VariantMakruk:
5364       pieces = makrukArray;
5365       nrCastlingRights = 0;
5366       startedFromSetupPosition = TRUE;
5367       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5368       break;
5369     case VariantTwoKings:
5370       pieces = twoKingsArray;
5371       break;
5372     case VariantCapaRandom:
5373       shuffleOpenings = TRUE;
5374     case VariantCapablanca:
5375       pieces = CapablancaArray;
5376       gameInfo.boardWidth = 10;
5377       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5378       break;
5379     case VariantGothic:
5380       pieces = GothicArray;
5381       gameInfo.boardWidth = 10;
5382       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5383       break;
5384     case VariantJanus:
5385       pieces = JanusArray;
5386       gameInfo.boardWidth = 10;
5387       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5388       nrCastlingRights = 6;
5389         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5390         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5391         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5392         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5393         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5394         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5395       break;
5396     case VariantFalcon:
5397       pieces = FalconArray;
5398       gameInfo.boardWidth = 10;
5399       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5400       break;
5401     case VariantXiangqi:
5402       pieces = XiangqiArray;
5403       gameInfo.boardWidth  = 9;
5404       gameInfo.boardHeight = 10;
5405       nrCastlingRights = 0;
5406       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5407       break;
5408     case VariantShogi:
5409       pieces = ShogiArray;
5410       gameInfo.boardWidth  = 9;
5411       gameInfo.boardHeight = 9;
5412       gameInfo.holdingsSize = 7;
5413       nrCastlingRights = 0;
5414       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5415       break;
5416     case VariantCourier:
5417       pieces = CourierArray;
5418       gameInfo.boardWidth  = 12;
5419       nrCastlingRights = 0;
5420       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5421       break;
5422     case VariantKnightmate:
5423       pieces = KnightmateArray;
5424       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5425       break;
5426     case VariantFairy:
5427       pieces = fairyArray;
5428       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5429       break;
5430     case VariantGreat:
5431       pieces = GreatArray;
5432       gameInfo.boardWidth = 10;
5433       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5434       gameInfo.holdingsSize = 8;
5435       break;
5436     case VariantSuper:
5437       pieces = FIDEArray;
5438       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5439       gameInfo.holdingsSize = 8;
5440       startedFromSetupPosition = TRUE;
5441       break;
5442     case VariantCrazyhouse:
5443     case VariantBughouse:
5444       pieces = FIDEArray;
5445       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5446       gameInfo.holdingsSize = 5;
5447       break;
5448     case VariantWildCastle:
5449       pieces = FIDEArray;
5450       /* !!?shuffle with kings guaranteed to be on d or e file */
5451       shuffleOpenings = 1;
5452       break;
5453     case VariantNoCastle:
5454       pieces = FIDEArray;
5455       nrCastlingRights = 0;
5456       /* !!?unconstrained back-rank shuffle */
5457       shuffleOpenings = 1;
5458       break;
5459     }
5460
5461     overrule = 0;
5462     if(appData.NrFiles >= 0) {
5463         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5464         gameInfo.boardWidth = appData.NrFiles;
5465     }
5466     if(appData.NrRanks >= 0) {
5467         gameInfo.boardHeight = appData.NrRanks;
5468     }
5469     if(appData.holdingsSize >= 0) {
5470         i = appData.holdingsSize;
5471         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5472         gameInfo.holdingsSize = i;
5473     }
5474     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5475     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5476         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5477
5478     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5479     if(pawnRow < 1) pawnRow = 1;
5480     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5481
5482     /* User pieceToChar list overrules defaults */
5483     if(appData.pieceToCharTable != NULL)
5484         SetCharTable(pieceToChar, appData.pieceToCharTable);
5485
5486     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5487
5488         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5489             s = (ChessSquare) 0; /* account holding counts in guard band */
5490         for( i=0; i<BOARD_HEIGHT; i++ )
5491             initialPosition[i][j] = s;
5492
5493         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5494         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5495         initialPosition[pawnRow][j] = WhitePawn;
5496         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5497         if(gameInfo.variant == VariantXiangqi) {
5498             if(j&1) {
5499                 initialPosition[pawnRow][j] =
5500                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5501                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5502                    initialPosition[2][j] = WhiteCannon;
5503                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5504                 }
5505             }
5506         }
5507         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5508     }
5509     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5510
5511             j=BOARD_LEFT+1;
5512             initialPosition[1][j] = WhiteBishop;
5513             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5514             j=BOARD_RGHT-2;
5515             initialPosition[1][j] = WhiteRook;
5516             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5517     }
5518
5519     if( nrCastlingRights == -1) {
5520         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5521         /*       This sets default castling rights from none to normal corners   */
5522         /* Variants with other castling rights must set them themselves above    */
5523         nrCastlingRights = 6;
5524
5525         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5526         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5527         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5528         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5529         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5530         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5531      }
5532
5533      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5534      if(gameInfo.variant == VariantGreat) { // promotion commoners
5535         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5536         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5537         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5538         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5539      }
5540   if (appData.debugMode) {
5541     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5542   }
5543     if(shuffleOpenings) {
5544         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5545         startedFromSetupPosition = TRUE;
5546     }
5547     if(startedFromPositionFile) {
5548       /* [HGM] loadPos: use PositionFile for every new game */
5549       CopyBoard(initialPosition, filePosition);
5550       for(i=0; i<nrCastlingRights; i++)
5551           initialRights[i] = filePosition[CASTLING][i];
5552       startedFromSetupPosition = TRUE;
5553     }
5554
5555     CopyBoard(boards[0], initialPosition);
5556
5557     if(oldx != gameInfo.boardWidth ||
5558        oldy != gameInfo.boardHeight ||
5559        oldh != gameInfo.holdingsWidth
5560 #ifdef GOTHIC
5561        || oldv == VariantGothic ||        // For licensing popups
5562        gameInfo.variant == VariantGothic
5563 #endif
5564 #ifdef FALCON
5565        || oldv == VariantFalcon ||
5566        gameInfo.variant == VariantFalcon
5567 #endif
5568                                          )
5569             InitDrawingSizes(-2 ,0);
5570
5571     if (redraw)
5572       DrawPosition(TRUE, boards[currentMove]);
5573 }
5574
5575 void
5576 SendBoard(cps, moveNum)
5577      ChessProgramState *cps;
5578      int moveNum;
5579 {
5580     char message[MSG_SIZ];
5581
5582     if (cps->useSetboard) {
5583       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5584       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5585       SendToProgram(message, cps);
5586       free(fen);
5587
5588     } else {
5589       ChessSquare *bp;
5590       int i, j;
5591       /* Kludge to set black to move, avoiding the troublesome and now
5592        * deprecated "black" command.
5593        */
5594       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5595         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5596
5597       SendToProgram("edit\n", cps);
5598       SendToProgram("#\n", cps);
5599       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5600         bp = &boards[moveNum][i][BOARD_LEFT];
5601         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5602           if ((int) *bp < (int) BlackPawn) {
5603             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5604                     AAA + j, ONE + i);
5605             if(message[0] == '+' || message[0] == '~') {
5606               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5607                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5608                         AAA + j, ONE + i);
5609             }
5610             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5611                 message[1] = BOARD_RGHT   - 1 - j + '1';
5612                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5613             }
5614             SendToProgram(message, cps);
5615           }
5616         }
5617       }
5618
5619       SendToProgram("c\n", cps);
5620       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5621         bp = &boards[moveNum][i][BOARD_LEFT];
5622         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5623           if (((int) *bp != (int) EmptySquare)
5624               && ((int) *bp >= (int) BlackPawn)) {
5625             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5626                     AAA + j, ONE + i);
5627             if(message[0] == '+' || message[0] == '~') {
5628               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5629                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5630                         AAA + j, ONE + i);
5631             }
5632             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5633                 message[1] = BOARD_RGHT   - 1 - j + '1';
5634                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5635             }
5636             SendToProgram(message, cps);
5637           }
5638         }
5639       }
5640
5641       SendToProgram(".\n", cps);
5642     }
5643     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5644 }
5645
5646 static int autoQueen; // [HGM] oneclick
5647
5648 int
5649 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5650 {
5651     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5652     /* [HGM] add Shogi promotions */
5653     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5654     ChessSquare piece;
5655     ChessMove moveType;
5656     Boolean premove;
5657
5658     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5659     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5660
5661     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5662       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5663         return FALSE;
5664
5665     piece = boards[currentMove][fromY][fromX];
5666     if(gameInfo.variant == VariantShogi) {
5667         promotionZoneSize = BOARD_HEIGHT/3;
5668         highestPromotingPiece = (int)WhiteFerz;
5669     } else if(gameInfo.variant == VariantMakruk) {
5670         promotionZoneSize = 3;
5671     }
5672
5673     // next weed out all moves that do not touch the promotion zone at all
5674     if((int)piece >= BlackPawn) {
5675         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5676              return FALSE;
5677         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5678     } else {
5679         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5680            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5681     }
5682
5683     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5684
5685     // weed out mandatory Shogi promotions
5686     if(gameInfo.variant == VariantShogi) {
5687         if(piece >= BlackPawn) {
5688             if(toY == 0 && piece == BlackPawn ||
5689                toY == 0 && piece == BlackQueen ||
5690                toY <= 1 && piece == BlackKnight) {
5691                 *promoChoice = '+';
5692                 return FALSE;
5693             }
5694         } else {
5695             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5696                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5697                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5698                 *promoChoice = '+';
5699                 return FALSE;
5700             }
5701         }
5702     }
5703
5704     // weed out obviously illegal Pawn moves
5705     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5706         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5707         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5708         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5709         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5710         // note we are not allowed to test for valid (non-)capture, due to premove
5711     }
5712
5713     // we either have a choice what to promote to, or (in Shogi) whether to promote
5714     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5715         *promoChoice = PieceToChar(BlackFerz);  // no choice
5716         return FALSE;
5717     }
5718     // no sense asking what we must promote to if it is going to explode...
5719     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5720         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5721         return FALSE;
5722     }
5723     if(autoQueen) { // predetermined
5724         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5725              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5726         else *promoChoice = PieceToChar(BlackQueen);
5727         return FALSE;
5728     }
5729
5730     // suppress promotion popup on illegal moves that are not premoves
5731     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5732               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5733     if(appData.testLegality && !premove) {
5734         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5735                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5736         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5737             return FALSE;
5738     }
5739
5740     return TRUE;
5741 }
5742
5743 int
5744 InPalace(row, column)
5745      int row, column;
5746 {   /* [HGM] for Xiangqi */
5747     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5748          column < (BOARD_WIDTH + 4)/2 &&
5749          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5750     return FALSE;
5751 }
5752
5753 int
5754 PieceForSquare (x, y)
5755      int x;
5756      int y;
5757 {
5758   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5759      return -1;
5760   else
5761      return boards[currentMove][y][x];
5762 }
5763
5764 int
5765 OKToStartUserMove(x, y)
5766      int x, y;
5767 {
5768     ChessSquare from_piece;
5769     int white_piece;
5770
5771     if (matchMode) return FALSE;
5772     if (gameMode == EditPosition) return TRUE;
5773
5774     if (x >= 0 && y >= 0)
5775       from_piece = boards[currentMove][y][x];
5776     else
5777       from_piece = EmptySquare;
5778
5779     if (from_piece == EmptySquare) return FALSE;
5780
5781     white_piece = (int)from_piece >= (int)WhitePawn &&
5782       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5783
5784     switch (gameMode) {
5785       case PlayFromGameFile:
5786       case AnalyzeFile:
5787       case TwoMachinesPlay:
5788       case EndOfGame:
5789         return FALSE;
5790
5791       case IcsObserving:
5792       case IcsIdle:
5793         return FALSE;
5794
5795       case MachinePlaysWhite:
5796       case IcsPlayingBlack:
5797         if (appData.zippyPlay) return FALSE;
5798         if (white_piece) {
5799             DisplayMoveError(_("You are playing Black"));
5800             return FALSE;
5801         }
5802         break;
5803
5804       case MachinePlaysBlack:
5805       case IcsPlayingWhite:
5806         if (appData.zippyPlay) return FALSE;
5807         if (!white_piece) {
5808             DisplayMoveError(_("You are playing White"));
5809             return FALSE;
5810         }
5811         break;
5812
5813       case EditGame:
5814         if (!white_piece && WhiteOnMove(currentMove)) {
5815             DisplayMoveError(_("It is White's turn"));
5816             return FALSE;
5817         }
5818         if (white_piece && !WhiteOnMove(currentMove)) {
5819             DisplayMoveError(_("It is Black's turn"));
5820             return FALSE;
5821         }
5822         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5823             /* Editing correspondence game history */
5824             /* Could disallow this or prompt for confirmation */
5825             cmailOldMove = -1;
5826         }
5827         break;
5828
5829       case BeginningOfGame:
5830         if (appData.icsActive) return FALSE;
5831         if (!appData.noChessProgram) {
5832             if (!white_piece) {
5833                 DisplayMoveError(_("You are playing White"));
5834                 return FALSE;
5835             }
5836         }
5837         break;
5838
5839       case Training:
5840         if (!white_piece && WhiteOnMove(currentMove)) {
5841             DisplayMoveError(_("It is White's turn"));
5842             return FALSE;
5843         }
5844         if (white_piece && !WhiteOnMove(currentMove)) {
5845             DisplayMoveError(_("It is Black's turn"));
5846             return FALSE;
5847         }
5848         break;
5849
5850       default:
5851       case IcsExamining:
5852         break;
5853     }
5854     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5855         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5856         && gameMode != AnalyzeFile && gameMode != Training) {
5857         DisplayMoveError(_("Displayed position is not current"));
5858         return FALSE;
5859     }
5860     return TRUE;
5861 }
5862
5863 Boolean
5864 OnlyMove(int *x, int *y, Boolean captures) {
5865     DisambiguateClosure cl;
5866     if (appData.zippyPlay) return FALSE;
5867     switch(gameMode) {
5868       case MachinePlaysBlack:
5869       case IcsPlayingWhite:
5870       case BeginningOfGame:
5871         if(!WhiteOnMove(currentMove)) return FALSE;
5872         break;
5873       case MachinePlaysWhite:
5874       case IcsPlayingBlack:
5875         if(WhiteOnMove(currentMove)) return FALSE;
5876         break;
5877       case EditGame:
5878         break;
5879       default:
5880         return FALSE;
5881     }
5882     cl.pieceIn = EmptySquare;
5883     cl.rfIn = *y;
5884     cl.ffIn = *x;
5885     cl.rtIn = -1;
5886     cl.ftIn = -1;
5887     cl.promoCharIn = NULLCHAR;
5888     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5889     if( cl.kind == NormalMove ||
5890         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5891         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5892         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5893       fromX = cl.ff;
5894       fromY = cl.rf;
5895       *x = cl.ft;
5896       *y = cl.rt;
5897       return TRUE;
5898     }
5899     if(cl.kind != ImpossibleMove) return FALSE;
5900     cl.pieceIn = EmptySquare;
5901     cl.rfIn = -1;
5902     cl.ffIn = -1;
5903     cl.rtIn = *y;
5904     cl.ftIn = *x;
5905     cl.promoCharIn = NULLCHAR;
5906     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5907     if( cl.kind == NormalMove ||
5908         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5909         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5910         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5911       fromX = cl.ff;
5912       fromY = cl.rf;
5913       *x = cl.ft;
5914       *y = cl.rt;
5915       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5916       return TRUE;
5917     }
5918     return FALSE;
5919 }
5920
5921 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5922 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5923 int lastLoadGameUseList = FALSE;
5924 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5925 ChessMove lastLoadGameStart = EndOfFile;
5926
5927 void
5928 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5929      int fromX, fromY, toX, toY;
5930      int promoChar;
5931 {
5932     ChessMove moveType;
5933     ChessSquare pdown, pup;
5934
5935     /* Check if the user is playing in turn.  This is complicated because we
5936        let the user "pick up" a piece before it is his turn.  So the piece he
5937        tried to pick up may have been captured by the time he puts it down!
5938        Therefore we use the color the user is supposed to be playing in this
5939        test, not the color of the piece that is currently on the starting
5940        square---except in EditGame mode, where the user is playing both
5941        sides; fortunately there the capture race can't happen.  (It can
5942        now happen in IcsExamining mode, but that's just too bad.  The user
5943        will get a somewhat confusing message in that case.)
5944        */
5945
5946     switch (gameMode) {
5947       case PlayFromGameFile:
5948       case AnalyzeFile:
5949       case TwoMachinesPlay:
5950       case EndOfGame:
5951       case IcsObserving:
5952       case IcsIdle:
5953         /* We switched into a game mode where moves are not accepted,
5954            perhaps while the mouse button was down. */
5955         return;
5956
5957       case MachinePlaysWhite:
5958         /* User is moving for Black */
5959         if (WhiteOnMove(currentMove)) {
5960             DisplayMoveError(_("It is White's turn"));
5961             return;
5962         }
5963         break;
5964
5965       case MachinePlaysBlack:
5966         /* User is moving for White */
5967         if (!WhiteOnMove(currentMove)) {
5968             DisplayMoveError(_("It is Black's turn"));
5969             return;
5970         }
5971         break;
5972
5973       case EditGame:
5974       case IcsExamining:
5975       case BeginningOfGame:
5976       case AnalyzeMode:
5977       case Training:
5978         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5979             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5980             /* User is moving for Black */
5981             if (WhiteOnMove(currentMove)) {
5982                 DisplayMoveError(_("It is White's turn"));
5983                 return;
5984             }
5985         } else {
5986             /* User is moving for White */
5987             if (!WhiteOnMove(currentMove)) {
5988                 DisplayMoveError(_("It is Black's turn"));
5989                 return;
5990             }
5991         }
5992         break;
5993
5994       case IcsPlayingBlack:
5995         /* User is moving for Black */
5996         if (WhiteOnMove(currentMove)) {
5997             if (!appData.premove) {
5998                 DisplayMoveError(_("It is White's turn"));
5999             } else if (toX >= 0 && toY >= 0) {
6000                 premoveToX = toX;
6001                 premoveToY = toY;
6002                 premoveFromX = fromX;
6003                 premoveFromY = fromY;
6004                 premovePromoChar = promoChar;
6005                 gotPremove = 1;
6006                 if (appData.debugMode)
6007                     fprintf(debugFP, "Got premove: fromX %d,"
6008                             "fromY %d, toX %d, toY %d\n",
6009                             fromX, fromY, toX, toY);
6010             }
6011             return;
6012         }
6013         break;
6014
6015       case IcsPlayingWhite:
6016         /* User is moving for White */
6017         if (!WhiteOnMove(currentMove)) {
6018             if (!appData.premove) {
6019                 DisplayMoveError(_("It is Black's turn"));
6020             } else if (toX >= 0 && toY >= 0) {
6021                 premoveToX = toX;
6022                 premoveToY = toY;
6023                 premoveFromX = fromX;
6024                 premoveFromY = fromY;
6025                 premovePromoChar = promoChar;
6026                 gotPremove = 1;
6027                 if (appData.debugMode)
6028                     fprintf(debugFP, "Got premove: fromX %d,"
6029                             "fromY %d, toX %d, toY %d\n",
6030                             fromX, fromY, toX, toY);
6031             }
6032             return;
6033         }
6034         break;
6035
6036       default:
6037         break;
6038
6039       case EditPosition:
6040         /* EditPosition, empty square, or different color piece;
6041            click-click move is possible */
6042         if (toX == -2 || toY == -2) {
6043             boards[0][fromY][fromX] = EmptySquare;
6044             DrawPosition(FALSE, boards[currentMove]);
6045             return;
6046         } else if (toX >= 0 && toY >= 0) {
6047             boards[0][toY][toX] = boards[0][fromY][fromX];
6048             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6049                 if(boards[0][fromY][0] != EmptySquare) {
6050                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6051                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6052                 }
6053             } else
6054             if(fromX == BOARD_RGHT+1) {
6055                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6056                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6057                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6058                 }
6059             } else
6060             boards[0][fromY][fromX] = EmptySquare;
6061             DrawPosition(FALSE, boards[currentMove]);
6062             return;
6063         }
6064         return;
6065     }
6066
6067     if(toX < 0 || toY < 0) return;
6068     pdown = boards[currentMove][fromY][fromX];
6069     pup = boards[currentMove][toY][toX];
6070
6071     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6072     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6073          if( pup != EmptySquare ) return;
6074          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6075            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6076                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6077            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6078            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6079            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6080            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6081          fromY = DROP_RANK;
6082     }
6083
6084     /* [HGM] always test for legality, to get promotion info */
6085     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6086                                          fromY, fromX, toY, toX, promoChar);
6087     /* [HGM] but possibly ignore an IllegalMove result */
6088     if (appData.testLegality) {
6089         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6090             DisplayMoveError(_("Illegal move"));
6091             return;
6092         }
6093     }
6094
6095     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6096 }
6097
6098 /* Common tail of UserMoveEvent and DropMenuEvent */
6099 int
6100 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6101      ChessMove moveType;
6102      int fromX, fromY, toX, toY;
6103      /*char*/int promoChar;
6104 {
6105     char *bookHit = 0;
6106
6107     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6108         // [HGM] superchess: suppress promotions to non-available piece
6109         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6110         if(WhiteOnMove(currentMove)) {
6111             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6112         } else {
6113             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6114         }
6115     }
6116
6117     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6118        move type in caller when we know the move is a legal promotion */
6119     if(moveType == NormalMove && promoChar)
6120         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6121
6122     /* [HGM] <popupFix> The following if has been moved here from
6123        UserMoveEvent(). Because it seemed to belong here (why not allow
6124        piece drops in training games?), and because it can only be
6125        performed after it is known to what we promote. */
6126     if (gameMode == Training) {
6127       /* compare the move played on the board to the next move in the
6128        * game. If they match, display the move and the opponent's response.
6129        * If they don't match, display an error message.
6130        */
6131       int saveAnimate;
6132       Board testBoard;
6133       CopyBoard(testBoard, boards[currentMove]);
6134       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6135
6136       if (CompareBoards(testBoard, boards[currentMove+1])) {
6137         ForwardInner(currentMove+1);
6138
6139         /* Autoplay the opponent's response.
6140          * if appData.animate was TRUE when Training mode was entered,
6141          * the response will be animated.
6142          */
6143         saveAnimate = appData.animate;
6144         appData.animate = animateTraining;
6145         ForwardInner(currentMove+1);
6146         appData.animate = saveAnimate;
6147
6148         /* check for the end of the game */
6149         if (currentMove >= forwardMostMove) {
6150           gameMode = PlayFromGameFile;
6151           ModeHighlight();
6152           SetTrainingModeOff();
6153           DisplayInformation(_("End of game"));
6154         }
6155       } else {
6156         DisplayError(_("Incorrect move"), 0);
6157       }
6158       return 1;
6159     }
6160
6161   /* Ok, now we know that the move is good, so we can kill
6162      the previous line in Analysis Mode */
6163   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6164                                 && currentMove < forwardMostMove) {
6165     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6166     else forwardMostMove = currentMove;
6167   }
6168
6169   /* If we need the chess program but it's dead, restart it */
6170   ResurrectChessProgram();
6171
6172   /* A user move restarts a paused game*/
6173   if (pausing)
6174     PauseEvent();
6175
6176   thinkOutput[0] = NULLCHAR;
6177
6178   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6179
6180   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6181
6182   if (gameMode == BeginningOfGame) {
6183     if (appData.noChessProgram) {
6184       gameMode = EditGame;
6185       SetGameInfo();
6186     } else {
6187       char buf[MSG_SIZ];
6188       gameMode = MachinePlaysBlack;
6189       StartClocks();
6190       SetGameInfo();
6191       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6192       DisplayTitle(buf);
6193       if (first.sendName) {
6194         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6195         SendToProgram(buf, &first);
6196       }
6197       StartClocks();
6198     }
6199     ModeHighlight();
6200   }
6201
6202   /* Relay move to ICS or chess engine */
6203   if (appData.icsActive) {
6204     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6205         gameMode == IcsExamining) {
6206       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6207         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6208         SendToICS("draw ");
6209         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6210       }
6211       // also send plain move, in case ICS does not understand atomic claims
6212       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6213       ics_user_moved = 1;
6214     }
6215   } else {
6216     if (first.sendTime && (gameMode == BeginningOfGame ||
6217                            gameMode == MachinePlaysWhite ||
6218                            gameMode == MachinePlaysBlack)) {
6219       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6220     }
6221     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6222          // [HGM] book: if program might be playing, let it use book
6223         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6224         first.maybeThinking = TRUE;
6225     } else SendMoveToProgram(forwardMostMove-1, &first);
6226     if (currentMove == cmailOldMove + 1) {
6227       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6228     }
6229   }
6230
6231   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6232
6233   switch (gameMode) {
6234   case EditGame:
6235     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6236     case MT_NONE:
6237     case MT_CHECK:
6238       break;
6239     case MT_CHECKMATE:
6240     case MT_STAINMATE:
6241       if (WhiteOnMove(currentMove)) {
6242         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6243       } else {
6244         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6245       }
6246       break;
6247     case MT_STALEMATE:
6248       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6249       break;
6250     }
6251     break;
6252
6253   case MachinePlaysBlack:
6254   case MachinePlaysWhite:
6255     /* disable certain menu options while machine is thinking */
6256     SetMachineThinkingEnables();
6257     break;
6258
6259   default:
6260     break;
6261   }
6262
6263   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6264
6265   if(bookHit) { // [HGM] book: simulate book reply
6266         static char bookMove[MSG_SIZ]; // a bit generous?
6267
6268         programStats.nodes = programStats.depth = programStats.time =
6269         programStats.score = programStats.got_only_move = 0;
6270         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6271
6272         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6273         strcat(bookMove, bookHit);
6274         HandleMachineMove(bookMove, &first);
6275   }
6276   return 1;
6277 }
6278
6279 void
6280 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6281      Board board;
6282      int flags;
6283      ChessMove kind;
6284      int rf, ff, rt, ft;
6285      VOIDSTAR closure;
6286 {
6287     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6288     Markers *m = (Markers *) closure;
6289     if(rf == fromY && ff == fromX)
6290         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6291                          || kind == WhiteCapturesEnPassant
6292                          || kind == BlackCapturesEnPassant);
6293     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6294 }
6295
6296 void
6297 MarkTargetSquares(int clear)
6298 {
6299   int x, y;
6300   if(!appData.markers || !appData.highlightDragging ||
6301      !appData.testLegality || gameMode == EditPosition) return;
6302   if(clear) {
6303     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6304   } else {
6305     int capt = 0;
6306     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6307     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6308       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6309       if(capt)
6310       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6311     }
6312   }
6313   DrawPosition(TRUE, NULL);
6314 }
6315
6316 int
6317 Explode(Board board, int fromX, int fromY, int toX, int toY)
6318 {
6319     if(gameInfo.variant == VariantAtomic &&
6320        (board[toY][toX] != EmptySquare ||                     // capture?
6321         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6322                          board[fromY][fromX] == BlackPawn   )
6323       )) {
6324         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6325         return TRUE;
6326     }
6327     return FALSE;
6328 }
6329
6330 void LeftClick(ClickType clickType, int xPix, int yPix)
6331 {
6332     int x, y;
6333     Boolean saveAnimate;
6334     static int second = 0, promotionChoice = 0, dragging = 0;
6335     char promoChoice = NULLCHAR;
6336
6337     if(appData.seekGraph && appData.icsActive && loggedOn &&
6338         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6339         SeekGraphClick(clickType, xPix, yPix, 0);
6340         return;
6341     }
6342
6343     if (clickType == Press) ErrorPopDown();
6344     MarkTargetSquares(1);
6345
6346     x = EventToSquare(xPix, BOARD_WIDTH);
6347     y = EventToSquare(yPix, BOARD_HEIGHT);
6348     if (!flipView && y >= 0) {
6349         y = BOARD_HEIGHT - 1 - y;
6350     }
6351     if (flipView && x >= 0) {
6352         x = BOARD_WIDTH - 1 - x;
6353     }
6354
6355     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6356         if(clickType == Release) return; // ignore upclick of click-click destination
6357         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6358         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6359         if(gameInfo.holdingsWidth &&
6360                 (WhiteOnMove(currentMove)
6361                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6362                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6363             // click in right holdings, for determining promotion piece
6364             ChessSquare p = boards[currentMove][y][x];
6365             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6366             if(p != EmptySquare) {
6367                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6368                 fromX = fromY = -1;
6369                 return;
6370             }
6371         }
6372         DrawPosition(FALSE, boards[currentMove]);
6373         return;
6374     }
6375
6376     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6377     if(clickType == Press
6378             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6379               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6380               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6381         return;
6382
6383     autoQueen = appData.alwaysPromoteToQueen;
6384
6385     if (fromX == -1) {
6386       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6387         if (clickType == Press) {
6388             /* First square */
6389             if (OKToStartUserMove(x, y)) {
6390                 fromX = x;
6391                 fromY = y;
6392                 second = 0;
6393                 MarkTargetSquares(0);
6394                 DragPieceBegin(xPix, yPix); dragging = 1;
6395                 if (appData.highlightDragging) {
6396                     SetHighlights(x, y, -1, -1);
6397                 }
6398             }
6399         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6400             DragPieceEnd(xPix, yPix); dragging = 0;
6401             DrawPosition(FALSE, NULL);
6402         }
6403         return;
6404       }
6405     }
6406
6407     /* fromX != -1 */
6408     if (clickType == Press && gameMode != EditPosition) {
6409         ChessSquare fromP;
6410         ChessSquare toP;
6411         int frc;
6412
6413         // ignore off-board to clicks
6414         if(y < 0 || x < 0) return;
6415
6416         /* Check if clicking again on the same color piece */
6417         fromP = boards[currentMove][fromY][fromX];
6418         toP = boards[currentMove][y][x];
6419         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6420         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6421              WhitePawn <= toP && toP <= WhiteKing &&
6422              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6423              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6424             (BlackPawn <= fromP && fromP <= BlackKing &&
6425              BlackPawn <= toP && toP <= BlackKing &&
6426              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6427              !(fromP == BlackKing && toP == BlackRook && frc))) {
6428             /* Clicked again on same color piece -- changed his mind */
6429             second = (x == fromX && y == fromY);
6430            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6431             if (appData.highlightDragging) {
6432                 SetHighlights(x, y, -1, -1);
6433             } else {
6434                 ClearHighlights();
6435             }
6436             if (OKToStartUserMove(x, y)) {
6437                 fromX = x;
6438                 fromY = y; dragging = 1;
6439                 MarkTargetSquares(0);
6440                 DragPieceBegin(xPix, yPix);
6441             }
6442             return;
6443            }
6444         }
6445         // ignore clicks on holdings
6446         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6447     }
6448
6449     if (clickType == Release && x == fromX && y == fromY) {
6450         DragPieceEnd(xPix, yPix); dragging = 0;
6451         if (appData.animateDragging) {
6452             /* Undo animation damage if any */
6453             DrawPosition(FALSE, NULL);
6454         }
6455         if (second) {
6456             /* Second up/down in same square; just abort move */
6457             second = 0;
6458             fromX = fromY = -1;
6459             ClearHighlights();
6460             gotPremove = 0;
6461             ClearPremoveHighlights();
6462         } else {
6463             /* First upclick in same square; start click-click mode */
6464             SetHighlights(x, y, -1, -1);
6465         }
6466         return;
6467     }
6468
6469     /* we now have a different from- and (possibly off-board) to-square */
6470     /* Completed move */
6471     toX = x;
6472     toY = y;
6473     saveAnimate = appData.animate;
6474     if (clickType == Press) {
6475         /* Finish clickclick move */
6476         if (appData.animate || appData.highlightLastMove) {
6477             SetHighlights(fromX, fromY, toX, toY);
6478         } else {
6479             ClearHighlights();
6480         }
6481     } else {
6482         /* Finish drag move */
6483         if (appData.highlightLastMove) {
6484             SetHighlights(fromX, fromY, toX, toY);
6485         } else {
6486             ClearHighlights();
6487         }
6488         DragPieceEnd(xPix, yPix); dragging = 0;
6489         /* Don't animate move and drag both */
6490         appData.animate = FALSE;
6491     }
6492
6493     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6494     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6495         ChessSquare piece = boards[currentMove][fromY][fromX];
6496         if(gameMode == EditPosition && piece != EmptySquare &&
6497            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6498             int n;
6499
6500             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6501                 n = PieceToNumber(piece - (int)BlackPawn);
6502                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6503                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6504                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6505             } else
6506             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6507                 n = PieceToNumber(piece);
6508                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6509                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6510                 boards[currentMove][n][BOARD_WIDTH-2]++;
6511             }
6512             boards[currentMove][fromY][fromX] = EmptySquare;
6513         }
6514         ClearHighlights();
6515         fromX = fromY = -1;
6516         DrawPosition(TRUE, boards[currentMove]);
6517         return;
6518     }
6519
6520     // off-board moves should not be highlighted
6521     if(x < 0 || y < 0) ClearHighlights();
6522
6523     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6524         SetHighlights(fromX, fromY, toX, toY);
6525         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6526             // [HGM] super: promotion to captured piece selected from holdings
6527             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6528             promotionChoice = TRUE;
6529             // kludge follows to temporarily execute move on display, without promoting yet
6530             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6531             boards[currentMove][toY][toX] = p;
6532             DrawPosition(FALSE, boards[currentMove]);
6533             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6534             boards[currentMove][toY][toX] = q;
6535             DisplayMessage("Click in holdings to choose piece", "");
6536             return;
6537         }
6538         PromotionPopUp();
6539     } else {
6540         int oldMove = currentMove;
6541         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6542         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6543         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6544         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6545            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6546             DrawPosition(TRUE, boards[currentMove]);
6547         fromX = fromY = -1;
6548     }
6549     appData.animate = saveAnimate;
6550     if (appData.animate || appData.animateDragging) {
6551         /* Undo animation damage if needed */
6552         DrawPosition(FALSE, NULL);
6553     }
6554 }
6555
6556 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6557 {   // front-end-free part taken out of PieceMenuPopup
6558     int whichMenu; int xSqr, ySqr;
6559
6560     if(seekGraphUp) { // [HGM] seekgraph
6561         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6562         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6563         return -2;
6564     }
6565
6566     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6567          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6568         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6569         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6570         if(action == Press)   {
6571             originalFlip = flipView;
6572             flipView = !flipView; // temporarily flip board to see game from partners perspective
6573             DrawPosition(TRUE, partnerBoard);
6574             DisplayMessage(partnerStatus, "");
6575             partnerUp = TRUE;
6576         } else if(action == Release) {
6577             flipView = originalFlip;
6578             DrawPosition(TRUE, boards[currentMove]);
6579             partnerUp = FALSE;
6580         }
6581         return -2;
6582     }
6583
6584     xSqr = EventToSquare(x, BOARD_WIDTH);
6585     ySqr = EventToSquare(y, BOARD_HEIGHT);
6586     if (action == Release) UnLoadPV(); // [HGM] pv
6587     if (action != Press) return -2; // return code to be ignored
6588     switch (gameMode) {
6589       case IcsExamining:
6590         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6591       case EditPosition:
6592         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6593         if (xSqr < 0 || ySqr < 0) return -1;\r
6594         whichMenu = 0; // edit-position menu
6595         break;
6596       case IcsObserving:
6597         if(!appData.icsEngineAnalyze) return -1;
6598       case IcsPlayingWhite:
6599       case IcsPlayingBlack:
6600         if(!appData.zippyPlay) goto noZip;
6601       case AnalyzeMode:
6602       case AnalyzeFile:
6603       case MachinePlaysWhite:
6604       case MachinePlaysBlack:
6605       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6606         if (!appData.dropMenu) {
6607           LoadPV(x, y);
6608           return 2; // flag front-end to grab mouse events
6609         }
6610         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6611            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6612       case EditGame:
6613       noZip:
6614         if (xSqr < 0 || ySqr < 0) return -1;
6615         if (!appData.dropMenu || appData.testLegality &&
6616             gameInfo.variant != VariantBughouse &&
6617             gameInfo.variant != VariantCrazyhouse) return -1;
6618         whichMenu = 1; // drop menu
6619         break;
6620       default:
6621         return -1;
6622     }
6623
6624     if (((*fromX = xSqr) < 0) ||
6625         ((*fromY = ySqr) < 0)) {
6626         *fromX = *fromY = -1;
6627         return -1;
6628     }
6629     if (flipView)
6630       *fromX = BOARD_WIDTH - 1 - *fromX;
6631     else
6632       *fromY = BOARD_HEIGHT - 1 - *fromY;
6633
6634     return whichMenu;
6635 }
6636
6637 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6638 {
6639 //    char * hint = lastHint;
6640     FrontEndProgramStats stats;
6641
6642     stats.which = cps == &first ? 0 : 1;
6643     stats.depth = cpstats->depth;
6644     stats.nodes = cpstats->nodes;
6645     stats.score = cpstats->score;
6646     stats.time = cpstats->time;
6647     stats.pv = cpstats->movelist;
6648     stats.hint = lastHint;
6649     stats.an_move_index = 0;
6650     stats.an_move_count = 0;
6651
6652     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6653         stats.hint = cpstats->move_name;
6654         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6655         stats.an_move_count = cpstats->nr_moves;
6656     }
6657
6658     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
6659
6660     SetProgramStats( &stats );
6661 }
6662
6663 void
6664 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6665 {       // count all piece types
6666         int p, f, r;
6667         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6668         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6669         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6670                 p = board[r][f];
6671                 pCnt[p]++;
6672                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6673                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6674                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6675                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6676                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6677                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6678         }
6679 }
6680
6681 int
6682 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6683 {
6684         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6685         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6686
6687         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6688         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6689         if(myPawns == 2 && nMine == 3) // KPP
6690             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6691         if(myPawns == 1 && nMine == 2) // KP
6692             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6693         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6694             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6695         if(myPawns) return FALSE;
6696         if(pCnt[WhiteRook+side])
6697             return pCnt[BlackRook-side] ||
6698                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6699                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6700                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6701         if(pCnt[WhiteCannon+side]) {
6702             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6703             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6704         }
6705         if(pCnt[WhiteKnight+side])
6706             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6707         return FALSE;
6708 }
6709
6710 int
6711 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6712 {
6713         VariantClass v = gameInfo.variant;
6714
6715         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6716         if(v == VariantShatranj) return TRUE; // always winnable through baring
6717         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6718         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6719
6720         if(v == VariantXiangqi) {
6721                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6722
6723                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6724                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6725                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6726                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6727                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6728                 if(stale) // we have at least one last-rank P plus perhaps C
6729                     return majors // KPKX
6730                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6731                 else // KCA*E*
6732                     return pCnt[WhiteFerz+side] // KCAK
6733                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6734                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6735                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6736
6737         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6738                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6739
6740                 if(nMine == 1) return FALSE; // bare King
6741                 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
6742                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6743                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6744                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6745                 if(pCnt[WhiteKnight+side])
6746                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6747                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6748                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6749                 if(nBishops)
6750                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6751                 if(pCnt[WhiteAlfil+side])
6752                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6753                 if(pCnt[WhiteWazir+side])
6754                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6755         }
6756
6757         return TRUE;
6758 }
6759
6760 int
6761 Adjudicate(ChessProgramState *cps)
6762 {       // [HGM] some adjudications useful with buggy engines
6763         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6764         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6765         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6766         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6767         int k, count = 0; static int bare = 1;
6768         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6769         Boolean canAdjudicate = !appData.icsActive;
6770
6771         // most tests only when we understand the game, i.e. legality-checking on
6772             if( appData.testLegality )
6773             {   /* [HGM] Some more adjudications for obstinate engines */
6774                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6775                 static int moveCount = 6;
6776                 ChessMove result;
6777                 char *reason = NULL;
6778
6779                 /* Count what is on board. */
6780                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6781
6782                 /* Some material-based adjudications that have to be made before stalemate test */
6783                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6784                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6785                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6786                      if(canAdjudicate && appData.checkMates) {
6787                          if(engineOpponent)
6788                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6789                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6790                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6791                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6792                          return 1;
6793                      }
6794                 }
6795
6796                 /* Bare King in Shatranj (loses) or Losers (wins) */
6797                 if( nrW == 1 || nrB == 1) {
6798                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6799                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6800                      if(canAdjudicate && appData.checkMates) {
6801                          if(engineOpponent)
6802                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6803                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6804                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6805                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6806                          return 1;
6807                      }
6808                   } else
6809                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6810                   {    /* bare King */
6811                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6812                         if(canAdjudicate && appData.checkMates) {
6813                             /* but only adjudicate if adjudication enabled */
6814                             if(engineOpponent)
6815                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6816                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6817                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6818                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6819                             return 1;
6820                         }
6821                   }
6822                 } else bare = 1;
6823
6824
6825             // don't wait for engine to announce game end if we can judge ourselves
6826             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6827               case MT_CHECK:
6828                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6829                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6830                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6831                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6832                             checkCnt++;
6833                         if(checkCnt >= 2) {
6834                             reason = "Xboard adjudication: 3rd check";
6835                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6836                             break;
6837                         }
6838                     }
6839                 }
6840               case MT_NONE:
6841               default:
6842                 break;
6843               case MT_STALEMATE:
6844               case MT_STAINMATE:
6845                 reason = "Xboard adjudication: Stalemate";
6846                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6847                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6848                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6849                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6850                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6851                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6852                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6853                                                                         EP_CHECKMATE : EP_WINS);
6854                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6855                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6856                 }
6857                 break;
6858               case MT_CHECKMATE:
6859                 reason = "Xboard adjudication: Checkmate";
6860                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6861                 break;
6862             }
6863
6864                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6865                     case EP_STALEMATE:
6866                         result = GameIsDrawn; break;
6867                     case EP_CHECKMATE:
6868                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6869                     case EP_WINS:
6870                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6871                     default:
6872                         result = EndOfFile;
6873                 }
6874                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6875                     if(engineOpponent)
6876                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6877                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6878                     GameEnds( result, reason, GE_XBOARD );
6879                     return 1;
6880                 }
6881
6882                 /* Next absolutely insufficient mating material. */
6883                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6884                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6885                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6886
6887                      /* always flag draws, for judging claims */
6888                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6889
6890                      if(canAdjudicate && appData.materialDraws) {
6891                          /* but only adjudicate them if adjudication enabled */
6892                          if(engineOpponent) {
6893                            SendToProgram("force\n", engineOpponent); // suppress reply
6894                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6895                          }
6896                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6897                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6898                          return 1;
6899                      }
6900                 }
6901
6902                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6903                 if(gameInfo.variant == VariantXiangqi ?
6904                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6905                  : nrW + nrB == 4 &&
6906                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6907                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6908                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6909                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6910                    ) ) {
6911                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6912                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6913                           if(engineOpponent) {
6914                             SendToProgram("force\n", engineOpponent); // suppress reply
6915                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6916                           }
6917                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6918                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6919                           return 1;
6920                      }
6921                 } else moveCount = 6;
6922             }
6923         if (appData.debugMode) { int i;
6924             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6925                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6926                     appData.drawRepeats);
6927             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6928               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6929
6930         }
6931
6932         // Repetition draws and 50-move rule can be applied independently of legality testing
6933
6934                 /* Check for rep-draws */
6935                 count = 0;
6936                 for(k = forwardMostMove-2;
6937                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6938                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6939                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6940                     k-=2)
6941                 {   int rights=0;
6942                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6943                         /* compare castling rights */
6944                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6945                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6946                                 rights++; /* King lost rights, while rook still had them */
6947                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6948                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6949                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6950                                    rights++; /* but at least one rook lost them */
6951                         }
6952                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6953                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6954                                 rights++;
6955                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6956                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6957                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6958                                    rights++;
6959                         }
6960                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6961                             && appData.drawRepeats > 1) {
6962                              /* adjudicate after user-specified nr of repeats */
6963                              int result = GameIsDrawn;
6964                              char *details = "XBoard adjudication: repetition draw";
6965                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6966                                 // [HGM] xiangqi: check for forbidden perpetuals
6967                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6968                                 for(m=forwardMostMove; m>k; m-=2) {
6969                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6970                                         ourPerpetual = 0; // the current mover did not always check
6971                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6972                                         hisPerpetual = 0; // the opponent did not always check
6973                                 }
6974                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6975                                                                         ourPerpetual, hisPerpetual);
6976                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6977                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6978                                     details = "Xboard adjudication: perpetual checking";
6979                                 } else
6980                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6981                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6982                                 } else
6983                                 // Now check for perpetual chases
6984                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6985                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6986                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6987                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6988                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6989                                         details = "Xboard adjudication: perpetual chasing";
6990                                     } else
6991                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6992                                         break; // Abort repetition-checking loop.
6993                                 }
6994                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6995                              }
6996                              if(engineOpponent) {
6997                                SendToProgram("force\n", engineOpponent); // suppress reply
6998                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6999                              }
7000                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7001                              GameEnds( result, details, GE_XBOARD );
7002                              return 1;
7003                         }
7004                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7005                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7006                     }
7007                 }
7008
7009                 /* Now we test for 50-move draws. Determine ply count */
7010                 count = forwardMostMove;
7011                 /* look for last irreversble move */
7012                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7013                     count--;
7014                 /* if we hit starting position, add initial plies */
7015                 if( count == backwardMostMove )
7016                     count -= initialRulePlies;
7017                 count = forwardMostMove - count;
7018                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7019                         // adjust reversible move counter for checks in Xiangqi
7020                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7021                         if(i < backwardMostMove) i = backwardMostMove;
7022                         while(i <= forwardMostMove) {
7023                                 lastCheck = inCheck; // check evasion does not count
7024                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7025                                 if(inCheck || lastCheck) count--; // check does not count
7026                                 i++;
7027                         }
7028                 }
7029                 if( count >= 100)
7030                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7031                          /* this is used to judge if draw claims are legal */
7032                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7033                          if(engineOpponent) {
7034                            SendToProgram("force\n", engineOpponent); // suppress reply
7035                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7036                          }
7037                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7038                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7039                          return 1;
7040                 }
7041
7042                 /* if draw offer is pending, treat it as a draw claim
7043                  * when draw condition present, to allow engines a way to
7044                  * claim draws before making their move to avoid a race
7045                  * condition occurring after their move
7046                  */
7047                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7048                          char *p = NULL;
7049                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7050                              p = "Draw claim: 50-move rule";
7051                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7052                              p = "Draw claim: 3-fold repetition";
7053                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7054                              p = "Draw claim: insufficient mating material";
7055                          if( p != NULL && canAdjudicate) {
7056                              if(engineOpponent) {
7057                                SendToProgram("force\n", engineOpponent); // suppress reply
7058                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7059                              }
7060                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7061                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7062                              return 1;
7063                          }
7064                 }
7065
7066                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7067                     if(engineOpponent) {
7068                       SendToProgram("force\n", engineOpponent); // suppress reply
7069                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7070                     }
7071                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7072                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7073                     return 1;
7074                 }
7075         return 0;
7076 }
7077
7078 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7079 {   // [HGM] book: this routine intercepts moves to simulate book replies
7080     char *bookHit = NULL;
7081
7082     //first determine if the incoming move brings opponent into his book
7083     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7084         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7085     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7086     if(bookHit != NULL && !cps->bookSuspend) {
7087         // make sure opponent is not going to reply after receiving move to book position
7088         SendToProgram("force\n", cps);
7089         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7090     }
7091     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7092     // now arrange restart after book miss
7093     if(bookHit) {
7094         // after a book hit we never send 'go', and the code after the call to this routine
7095         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7096         char buf[MSG_SIZ];
7097         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7098         SendToProgram(buf, cps);
7099         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7100     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7101         SendToProgram("go\n", cps);
7102         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7103     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7104         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7105             SendToProgram("go\n", cps);
7106         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7107     }
7108     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7109 }
7110
7111 char *savedMessage;
7112 ChessProgramState *savedState;
7113 void DeferredBookMove(void)
7114 {
7115         if(savedState->lastPing != savedState->lastPong)
7116                     ScheduleDelayedEvent(DeferredBookMove, 10);
7117         else
7118         HandleMachineMove(savedMessage, savedState);
7119 }
7120
7121 void
7122 HandleMachineMove(message, cps)
7123      char *message;
7124      ChessProgramState *cps;
7125 {
7126     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7127     char realname[MSG_SIZ];
7128     int fromX, fromY, toX, toY;
7129     ChessMove moveType;
7130     char promoChar;
7131     char *p;
7132     int machineWhite;
7133     char *bookHit;
7134
7135     cps->userError = 0;
7136
7137 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7138     /*
7139      * Kludge to ignore BEL characters
7140      */
7141     while (*message == '\007') message++;
7142
7143     /*
7144      * [HGM] engine debug message: ignore lines starting with '#' character
7145      */
7146     if(cps->debug && *message == '#') return;
7147
7148     /*
7149      * Look for book output
7150      */
7151     if (cps == &first && bookRequested) {
7152         if (message[0] == '\t' || message[0] == ' ') {
7153             /* Part of the book output is here; append it */
7154             strcat(bookOutput, message);
7155             strcat(bookOutput, "  \n");
7156             return;
7157         } else if (bookOutput[0] != NULLCHAR) {
7158             /* All of book output has arrived; display it */
7159             char *p = bookOutput;
7160             while (*p != NULLCHAR) {
7161                 if (*p == '\t') *p = ' ';
7162                 p++;
7163             }
7164             DisplayInformation(bookOutput);
7165             bookRequested = FALSE;
7166             /* Fall through to parse the current output */
7167         }
7168     }
7169
7170     /*
7171      * Look for machine move.
7172      */
7173     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7174         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7175     {
7176         /* This method is only useful on engines that support ping */
7177         if (cps->lastPing != cps->lastPong) {
7178           if (gameMode == BeginningOfGame) {
7179             /* Extra move from before last new; ignore */
7180             if (appData.debugMode) {
7181                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7182             }
7183           } else {
7184             if (appData.debugMode) {
7185                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7186                         cps->which, gameMode);
7187             }
7188
7189             SendToProgram("undo\n", cps);
7190           }
7191           return;
7192         }
7193
7194         switch (gameMode) {
7195           case BeginningOfGame:
7196             /* Extra move from before last reset; ignore */
7197             if (appData.debugMode) {
7198                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7199             }
7200             return;
7201
7202           case EndOfGame:
7203           case IcsIdle:
7204           default:
7205             /* Extra move after we tried to stop.  The mode test is
7206                not a reliable way of detecting this problem, but it's
7207                the best we can do on engines that don't support ping.
7208             */
7209             if (appData.debugMode) {
7210                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7211                         cps->which, gameMode);
7212             }
7213             SendToProgram("undo\n", cps);
7214             return;
7215
7216           case MachinePlaysWhite:
7217           case IcsPlayingWhite:
7218             machineWhite = TRUE;
7219             break;
7220
7221           case MachinePlaysBlack:
7222           case IcsPlayingBlack:
7223             machineWhite = FALSE;
7224             break;
7225
7226           case TwoMachinesPlay:
7227             machineWhite = (cps->twoMachinesColor[0] == 'w');
7228             break;
7229         }
7230         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7231             if (appData.debugMode) {
7232                 fprintf(debugFP,
7233                         "Ignoring move out of turn by %s, gameMode %d"
7234                         ", forwardMost %d\n",
7235                         cps->which, gameMode, forwardMostMove);
7236             }
7237             return;
7238         }
7239
7240     if (appData.debugMode) { int f = forwardMostMove;
7241         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7242                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7243                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7244     }
7245         if(cps->alphaRank) AlphaRank(machineMove, 4);
7246         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7247                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7248             /* Machine move could not be parsed; ignore it. */
7249           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7250                     machineMove, cps->which);
7251             DisplayError(buf1, 0);
7252             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7253                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7254             if (gameMode == TwoMachinesPlay) {
7255               GameEnds(machineWhite ? BlackWins : WhiteWins,
7256                        buf1, GE_XBOARD);
7257             }
7258             return;
7259         }
7260
7261         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7262         /* So we have to redo legality test with true e.p. status here,  */
7263         /* to make sure an illegal e.p. capture does not slip through,   */
7264         /* to cause a forfeit on a justified illegal-move complaint      */
7265         /* of the opponent.                                              */
7266         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7267            ChessMove moveType;
7268            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7269                              fromY, fromX, toY, toX, promoChar);
7270             if (appData.debugMode) {
7271                 int i;
7272                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7273                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7274                 fprintf(debugFP, "castling rights\n");
7275             }
7276             if(moveType == IllegalMove) {
7277               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7278                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7279                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7280                            buf1, GE_XBOARD);
7281                 return;
7282            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7283            /* [HGM] Kludge to handle engines that send FRC-style castling
7284               when they shouldn't (like TSCP-Gothic) */
7285            switch(moveType) {
7286              case WhiteASideCastleFR:
7287              case BlackASideCastleFR:
7288                toX+=2;
7289                currentMoveString[2]++;
7290                break;
7291              case WhiteHSideCastleFR:
7292              case BlackHSideCastleFR:
7293                toX--;
7294                currentMoveString[2]--;
7295                break;
7296              default: ; // nothing to do, but suppresses warning of pedantic compilers
7297            }
7298         }
7299         hintRequested = FALSE;
7300         lastHint[0] = NULLCHAR;
7301         bookRequested = FALSE;
7302         /* Program may be pondering now */
7303         cps->maybeThinking = TRUE;
7304         if (cps->sendTime == 2) cps->sendTime = 1;
7305         if (cps->offeredDraw) cps->offeredDraw--;
7306
7307         /* currentMoveString is set as a side-effect of ParseOneMove */
7308         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7309         strcat(machineMove, "\n");
7310         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7311
7312         /* [AS] Save move info*/
7313         pvInfoList[ forwardMostMove ].score = programStats.score;
7314         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7315         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7316
7317         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7318
7319         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7320         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7321             int count = 0;
7322
7323             while( count < adjudicateLossPlies ) {
7324                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7325
7326                 if( count & 1 ) {
7327                     score = -score; /* Flip score for winning side */
7328                 }
7329
7330                 if( score > adjudicateLossThreshold ) {
7331                     break;
7332                 }
7333
7334                 count++;
7335             }
7336
7337             if( count >= adjudicateLossPlies ) {
7338                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7339
7340                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7341                     "Xboard adjudication",
7342                     GE_XBOARD );
7343
7344                 return;
7345             }
7346         }
7347
7348         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7349
7350 #if ZIPPY
7351         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7352             first.initDone) {
7353           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7354                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7355                 SendToICS("draw ");
7356                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7357           }
7358           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7359           ics_user_moved = 1;
7360           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7361                 char buf[3*MSG_SIZ];
7362
7363                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7364                         programStats.score / 100.,
7365                         programStats.depth,
7366                         programStats.time / 100.,
7367                         (unsigned int)programStats.nodes,
7368                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7369                         programStats.movelist);
7370                 SendToICS(buf);
7371 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7372           }
7373         }
7374 #endif
7375
7376         /* [AS] Clear stats for next move */
7377         ClearProgramStats();
7378         thinkOutput[0] = NULLCHAR;
7379         hiddenThinkOutputState = 0;
7380
7381         bookHit = NULL;
7382         if (gameMode == TwoMachinesPlay) {
7383             /* [HGM] relaying draw offers moved to after reception of move */
7384             /* and interpreting offer as claim if it brings draw condition */
7385             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7386                 SendToProgram("draw\n", cps->other);
7387             }
7388             if (cps->other->sendTime) {
7389                 SendTimeRemaining(cps->other,
7390                                   cps->other->twoMachinesColor[0] == 'w');
7391             }
7392             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7393             if (firstMove && !bookHit) {
7394                 firstMove = FALSE;
7395                 if (cps->other->useColors) {
7396                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7397                 }
7398                 SendToProgram("go\n", cps->other);
7399             }
7400             cps->other->maybeThinking = TRUE;
7401         }
7402
7403         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7404
7405         if (!pausing && appData.ringBellAfterMoves) {
7406             RingBell();
7407         }
7408
7409         /*
7410          * Reenable menu items that were disabled while
7411          * machine was thinking
7412          */
7413         if (gameMode != TwoMachinesPlay)
7414             SetUserThinkingEnables();
7415
7416         // [HGM] book: after book hit opponent has received move and is now in force mode
7417         // force the book reply into it, and then fake that it outputted this move by jumping
7418         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7419         if(bookHit) {
7420                 static char bookMove[MSG_SIZ]; // a bit generous?
7421
7422                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7423                 strcat(bookMove, bookHit);
7424                 message = bookMove;
7425                 cps = cps->other;
7426                 programStats.nodes = programStats.depth = programStats.time =
7427                 programStats.score = programStats.got_only_move = 0;
7428                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7429
7430                 if(cps->lastPing != cps->lastPong) {
7431                     savedMessage = message; // args for deferred call
7432                     savedState = cps;
7433                     ScheduleDelayedEvent(DeferredBookMove, 10);
7434                     return;
7435                 }
7436                 goto FakeBookMove;
7437         }
7438
7439         return;
7440     }
7441
7442     /* Set special modes for chess engines.  Later something general
7443      *  could be added here; for now there is just one kludge feature,
7444      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7445      *  when "xboard" is given as an interactive command.
7446      */
7447     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7448         cps->useSigint = FALSE;
7449         cps->useSigterm = FALSE;
7450     }
7451     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7452       ParseFeatures(message+8, cps);
7453       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7454     }
7455
7456     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7457       int dummy, s=6; char buf[MSG_SIZ];
7458       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7459       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7460       ParseFEN(boards[0], &dummy, message+s);
7461       DrawPosition(TRUE, boards[0]);
7462       startedFromSetupPosition = TRUE;
7463       return;
7464     }
7465     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7466      * want this, I was asked to put it in, and obliged.
7467      */
7468     if (!strncmp(message, "setboard ", 9)) {
7469         Board initial_position;
7470
7471         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7472
7473         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7474             DisplayError(_("Bad FEN received from engine"), 0);
7475             return ;
7476         } else {
7477            Reset(TRUE, FALSE);
7478            CopyBoard(boards[0], initial_position);
7479            initialRulePlies = FENrulePlies;
7480            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7481            else gameMode = MachinePlaysBlack;
7482            DrawPosition(FALSE, boards[currentMove]);
7483         }
7484         return;
7485     }
7486
7487     /*
7488      * Look for communication commands
7489      */
7490     if (!strncmp(message, "telluser ", 9)) {
7491         if(message[9] == '\\' && message[10] == '\\')
7492             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7493         DisplayNote(message + 9);
7494         return;
7495     }
7496     if (!strncmp(message, "tellusererror ", 14)) {
7497         cps->userError = 1;
7498         if(message[14] == '\\' && message[15] == '\\')
7499             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7500         DisplayError(message + 14, 0);
7501         return;
7502     }
7503     if (!strncmp(message, "tellopponent ", 13)) {
7504       if (appData.icsActive) {
7505         if (loggedOn) {
7506           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7507           SendToICS(buf1);
7508         }
7509       } else {
7510         DisplayNote(message + 13);
7511       }
7512       return;
7513     }
7514     if (!strncmp(message, "tellothers ", 11)) {
7515       if (appData.icsActive) {
7516         if (loggedOn) {
7517           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7518           SendToICS(buf1);
7519         }
7520       }
7521       return;
7522     }
7523     if (!strncmp(message, "tellall ", 8)) {
7524       if (appData.icsActive) {
7525         if (loggedOn) {
7526           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7527           SendToICS(buf1);
7528         }
7529       } else {
7530         DisplayNote(message + 8);
7531       }
7532       return;
7533     }
7534     if (strncmp(message, "warning", 7) == 0) {
7535         /* Undocumented feature, use tellusererror in new code */
7536         DisplayError(message, 0);
7537         return;
7538     }
7539     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7540         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7541         strcat(realname, " query");
7542         AskQuestion(realname, buf2, buf1, cps->pr);
7543         return;
7544     }
7545     /* Commands from the engine directly to ICS.  We don't allow these to be
7546      *  sent until we are logged on. Crafty kibitzes have been known to
7547      *  interfere with the login process.
7548      */
7549     if (loggedOn) {
7550         if (!strncmp(message, "tellics ", 8)) {
7551             SendToICS(message + 8);
7552             SendToICS("\n");
7553             return;
7554         }
7555         if (!strncmp(message, "tellicsnoalias ", 15)) {
7556             SendToICS(ics_prefix);
7557             SendToICS(message + 15);
7558             SendToICS("\n");
7559             return;
7560         }
7561         /* The following are for backward compatibility only */
7562         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7563             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7564             SendToICS(ics_prefix);
7565             SendToICS(message);
7566             SendToICS("\n");
7567             return;
7568         }
7569     }
7570     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7571         return;
7572     }
7573     /*
7574      * If the move is illegal, cancel it and redraw the board.
7575      * Also deal with other error cases.  Matching is rather loose
7576      * here to accommodate engines written before the spec.
7577      */
7578     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7579         strncmp(message, "Error", 5) == 0) {
7580         if (StrStr(message, "name") ||
7581             StrStr(message, "rating") || StrStr(message, "?") ||
7582             StrStr(message, "result") || StrStr(message, "board") ||
7583             StrStr(message, "bk") || StrStr(message, "computer") ||
7584             StrStr(message, "variant") || StrStr(message, "hint") ||
7585             StrStr(message, "random") || StrStr(message, "depth") ||
7586             StrStr(message, "accepted")) {
7587             return;
7588         }
7589         if (StrStr(message, "protover")) {
7590           /* Program is responding to input, so it's apparently done
7591              initializing, and this error message indicates it is
7592              protocol version 1.  So we don't need to wait any longer
7593              for it to initialize and send feature commands. */
7594           FeatureDone(cps, 1);
7595           cps->protocolVersion = 1;
7596           return;
7597         }
7598         cps->maybeThinking = FALSE;
7599
7600         if (StrStr(message, "draw")) {
7601             /* Program doesn't have "draw" command */
7602             cps->sendDrawOffers = 0;
7603             return;
7604         }
7605         if (cps->sendTime != 1 &&
7606             (StrStr(message, "time") || StrStr(message, "otim"))) {
7607           /* Program apparently doesn't have "time" or "otim" command */
7608           cps->sendTime = 0;
7609           return;
7610         }
7611         if (StrStr(message, "analyze")) {
7612             cps->analysisSupport = FALSE;
7613             cps->analyzing = FALSE;
7614             Reset(FALSE, TRUE);
7615             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7616             DisplayError(buf2, 0);
7617             return;
7618         }
7619         if (StrStr(message, "(no matching move)st")) {
7620           /* Special kludge for GNU Chess 4 only */
7621           cps->stKludge = TRUE;
7622           SendTimeControl(cps, movesPerSession, timeControl,
7623                           timeIncrement, appData.searchDepth,
7624                           searchTime);
7625           return;
7626         }
7627         if (StrStr(message, "(no matching move)sd")) {
7628           /* Special kludge for GNU Chess 4 only */
7629           cps->sdKludge = TRUE;
7630           SendTimeControl(cps, movesPerSession, timeControl,
7631                           timeIncrement, appData.searchDepth,
7632                           searchTime);
7633           return;
7634         }
7635         if (!StrStr(message, "llegal")) {
7636             return;
7637         }
7638         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7639             gameMode == IcsIdle) return;
7640         if (forwardMostMove <= backwardMostMove) return;
7641         if (pausing) PauseEvent();
7642       if(appData.forceIllegal) {
7643             // [HGM] illegal: machine refused move; force position after move into it
7644           SendToProgram("force\n", cps);
7645           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7646                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7647                 // when black is to move, while there might be nothing on a2 or black
7648                 // might already have the move. So send the board as if white has the move.
7649                 // But first we must change the stm of the engine, as it refused the last move
7650                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7651                 if(WhiteOnMove(forwardMostMove)) {
7652                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7653                     SendBoard(cps, forwardMostMove); // kludgeless board
7654                 } else {
7655                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7656                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7657                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7658                 }
7659           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7660             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7661                  gameMode == TwoMachinesPlay)
7662               SendToProgram("go\n", cps);
7663             return;
7664       } else
7665         if (gameMode == PlayFromGameFile) {
7666             /* Stop reading this game file */
7667             gameMode = EditGame;
7668             ModeHighlight();
7669         }
7670         currentMove = forwardMostMove-1;
7671         DisplayMove(currentMove-1); /* before DisplayMoveError */
7672         SwitchClocks(forwardMostMove-1); // [HGM] race
7673         DisplayBothClocks();
7674         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7675                 parseList[currentMove], cps->which);
7676         DisplayMoveError(buf1);
7677         DrawPosition(FALSE, boards[currentMove]);
7678
7679         /* [HGM] illegal-move claim should forfeit game when Xboard */
7680         /* only passes fully legal moves                            */
7681         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7682             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7683                                 "False illegal-move claim", GE_XBOARD );
7684         }
7685         return;
7686     }
7687     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7688         /* Program has a broken "time" command that
7689            outputs a string not ending in newline.
7690            Don't use it. */
7691         cps->sendTime = 0;
7692     }
7693
7694     /*
7695      * If chess program startup fails, exit with an error message.
7696      * Attempts to recover here are futile.
7697      */
7698     if ((StrStr(message, "unknown host") != NULL)
7699         || (StrStr(message, "No remote directory") != NULL)
7700         || (StrStr(message, "not found") != NULL)
7701         || (StrStr(message, "No such file") != NULL)
7702         || (StrStr(message, "can't alloc") != NULL)
7703         || (StrStr(message, "Permission denied") != NULL)) {
7704
7705         cps->maybeThinking = FALSE;
7706         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7707                 cps->which, cps->program, cps->host, message);
7708         RemoveInputSource(cps->isr);
7709         DisplayFatalError(buf1, 0, 1);
7710         return;
7711     }
7712
7713     /*
7714      * Look for hint output
7715      */
7716     if (sscanf(message, "Hint: %s", buf1) == 1) {
7717         if (cps == &first && hintRequested) {
7718             hintRequested = FALSE;
7719             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7720                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7721                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7722                                     PosFlags(forwardMostMove),
7723                                     fromY, fromX, toY, toX, promoChar, buf1);
7724                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7725                 DisplayInformation(buf2);
7726             } else {
7727                 /* Hint move could not be parsed!? */
7728               snprintf(buf2, sizeof(buf2),
7729                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7730                         buf1, cps->which);
7731                 DisplayError(buf2, 0);
7732             }
7733         } else {
7734           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7735         }
7736         return;
7737     }
7738
7739     /*
7740      * Ignore other messages if game is not in progress
7741      */
7742     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7743         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7744
7745     /*
7746      * look for win, lose, draw, or draw offer
7747      */
7748     if (strncmp(message, "1-0", 3) == 0) {
7749         char *p, *q, *r = "";
7750         p = strchr(message, '{');
7751         if (p) {
7752             q = strchr(p, '}');
7753             if (q) {
7754                 *q = NULLCHAR;
7755                 r = p + 1;
7756             }
7757         }
7758         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7759         return;
7760     } else if (strncmp(message, "0-1", 3) == 0) {
7761         char *p, *q, *r = "";
7762         p = strchr(message, '{');
7763         if (p) {
7764             q = strchr(p, '}');
7765             if (q) {
7766                 *q = NULLCHAR;
7767                 r = p + 1;
7768             }
7769         }
7770         /* Kludge for Arasan 4.1 bug */
7771         if (strcmp(r, "Black resigns") == 0) {
7772             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7773             return;
7774         }
7775         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7776         return;
7777     } else if (strncmp(message, "1/2", 3) == 0) {
7778         char *p, *q, *r = "";
7779         p = strchr(message, '{');
7780         if (p) {
7781             q = strchr(p, '}');
7782             if (q) {
7783                 *q = NULLCHAR;
7784                 r = p + 1;
7785             }
7786         }
7787
7788         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7789         return;
7790
7791     } else if (strncmp(message, "White resign", 12) == 0) {
7792         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7793         return;
7794     } else if (strncmp(message, "Black resign", 12) == 0) {
7795         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7796         return;
7797     } else if (strncmp(message, "White matches", 13) == 0 ||
7798                strncmp(message, "Black matches", 13) == 0   ) {
7799         /* [HGM] ignore GNUShogi noises */
7800         return;
7801     } else if (strncmp(message, "White", 5) == 0 &&
7802                message[5] != '(' &&
7803                StrStr(message, "Black") == NULL) {
7804         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7805         return;
7806     } else if (strncmp(message, "Black", 5) == 0 &&
7807                message[5] != '(') {
7808         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7809         return;
7810     } else if (strcmp(message, "resign") == 0 ||
7811                strcmp(message, "computer resigns") == 0) {
7812         switch (gameMode) {
7813           case MachinePlaysBlack:
7814           case IcsPlayingBlack:
7815             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7816             break;
7817           case MachinePlaysWhite:
7818           case IcsPlayingWhite:
7819             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7820             break;
7821           case TwoMachinesPlay:
7822             if (cps->twoMachinesColor[0] == 'w')
7823               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7824             else
7825               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7826             break;
7827           default:
7828             /* can't happen */
7829             break;
7830         }
7831         return;
7832     } else if (strncmp(message, "opponent mates", 14) == 0) {
7833         switch (gameMode) {
7834           case MachinePlaysBlack:
7835           case IcsPlayingBlack:
7836             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7837             break;
7838           case MachinePlaysWhite:
7839           case IcsPlayingWhite:
7840             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7841             break;
7842           case TwoMachinesPlay:
7843             if (cps->twoMachinesColor[0] == 'w')
7844               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7845             else
7846               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7847             break;
7848           default:
7849             /* can't happen */
7850             break;
7851         }
7852         return;
7853     } else if (strncmp(message, "computer mates", 14) == 0) {
7854         switch (gameMode) {
7855           case MachinePlaysBlack:
7856           case IcsPlayingBlack:
7857             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7858             break;
7859           case MachinePlaysWhite:
7860           case IcsPlayingWhite:
7861             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7862             break;
7863           case TwoMachinesPlay:
7864             if (cps->twoMachinesColor[0] == 'w')
7865               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7866             else
7867               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7868             break;
7869           default:
7870             /* can't happen */
7871             break;
7872         }
7873         return;
7874     } else if (strncmp(message, "checkmate", 9) == 0) {
7875         if (WhiteOnMove(forwardMostMove)) {
7876             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7877         } else {
7878             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7879         }
7880         return;
7881     } else if (strstr(message, "Draw") != NULL ||
7882                strstr(message, "game is a draw") != NULL) {
7883         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7884         return;
7885     } else if (strstr(message, "offer") != NULL &&
7886                strstr(message, "draw") != NULL) {
7887 #if ZIPPY
7888         if (appData.zippyPlay && first.initDone) {
7889             /* Relay offer to ICS */
7890             SendToICS(ics_prefix);
7891             SendToICS("draw\n");
7892         }
7893 #endif
7894         cps->offeredDraw = 2; /* valid until this engine moves twice */
7895         if (gameMode == TwoMachinesPlay) {
7896             if (cps->other->offeredDraw) {
7897                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7898             /* [HGM] in two-machine mode we delay relaying draw offer      */
7899             /* until after we also have move, to see if it is really claim */
7900             }
7901         } else if (gameMode == MachinePlaysWhite ||
7902                    gameMode == MachinePlaysBlack) {
7903           if (userOfferedDraw) {
7904             DisplayInformation(_("Machine accepts your draw offer"));
7905             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7906           } else {
7907             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7908           }
7909         }
7910     }
7911
7912
7913     /*
7914      * Look for thinking output
7915      */
7916     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7917           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7918                                 ) {
7919         int plylev, mvleft, mvtot, curscore, time;
7920         char mvname[MOVE_LEN];
7921         u64 nodes; // [DM]
7922         char plyext;
7923         int ignore = FALSE;
7924         int prefixHint = FALSE;
7925         mvname[0] = NULLCHAR;
7926
7927         switch (gameMode) {
7928           case MachinePlaysBlack:
7929           case IcsPlayingBlack:
7930             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7931             break;
7932           case MachinePlaysWhite:
7933           case IcsPlayingWhite:
7934             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7935             break;
7936           case AnalyzeMode:
7937           case AnalyzeFile:
7938             break;
7939           case IcsObserving: /* [DM] icsEngineAnalyze */
7940             if (!appData.icsEngineAnalyze) ignore = TRUE;
7941             break;
7942           case TwoMachinesPlay:
7943             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7944                 ignore = TRUE;
7945             }
7946             break;
7947           default:
7948             ignore = TRUE;
7949             break;
7950         }
7951
7952         if (!ignore) {
7953             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7954             buf1[0] = NULLCHAR;
7955             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7956                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7957
7958                 if (plyext != ' ' && plyext != '\t') {
7959                     time *= 100;
7960                 }
7961
7962                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7963                 if( cps->scoreIsAbsolute &&
7964                     ( gameMode == MachinePlaysBlack ||
7965                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7966                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7967                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7968                      !WhiteOnMove(currentMove)
7969                     ) )
7970                 {
7971                     curscore = -curscore;
7972                 }
7973
7974
7975                 tempStats.depth = plylev;
7976                 tempStats.nodes = nodes;
7977                 tempStats.time = time;
7978                 tempStats.score = curscore;
7979                 tempStats.got_only_move = 0;
7980
7981                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7982                         int ticklen;
7983
7984                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7985                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7986                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7987                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7988                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7989                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7990                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7991                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7992                 }
7993
7994                 /* Buffer overflow protection */
7995                 if (buf1[0] != NULLCHAR) {
7996                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7997                         && appData.debugMode) {
7998                         fprintf(debugFP,
7999                                 "PV is too long; using the first %u bytes.\n",
8000                                 (unsigned) sizeof(tempStats.movelist) - 1);
8001                     }
8002
8003                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8004                 } else {
8005                     sprintf(tempStats.movelist, " no PV\n");
8006                 }
8007
8008                 if (tempStats.seen_stat) {
8009                     tempStats.ok_to_send = 1;
8010                 }
8011
8012                 if (strchr(tempStats.movelist, '(') != NULL) {
8013                     tempStats.line_is_book = 1;
8014                     tempStats.nr_moves = 0;
8015                     tempStats.moves_left = 0;
8016                 } else {
8017                     tempStats.line_is_book = 0;
8018                 }
8019
8020                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8021                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8022
8023                 SendProgramStatsToFrontend( cps, &tempStats );
8024
8025                 /*
8026                     [AS] Protect the thinkOutput buffer from overflow... this
8027                     is only useful if buf1 hasn't overflowed first!
8028                 */
8029                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8030                          plylev,
8031                          (gameMode == TwoMachinesPlay ?
8032                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8033                          ((double) curscore) / 100.0,
8034                          prefixHint ? lastHint : "",
8035                          prefixHint ? " " : "" );
8036
8037                 if( buf1[0] != NULLCHAR ) {
8038                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8039
8040                     if( strlen(buf1) > max_len ) {
8041                         if( appData.debugMode) {
8042                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8043                         }
8044                         buf1[max_len+1] = '\0';
8045                     }
8046
8047                     strcat( thinkOutput, buf1 );
8048                 }
8049
8050                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8051                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8052                     DisplayMove(currentMove - 1);
8053                 }
8054                 return;
8055
8056             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8057                 /* crafty (9.25+) says "(only move) <move>"
8058                  * if there is only 1 legal move
8059                  */
8060                 sscanf(p, "(only move) %s", buf1);
8061                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8062                 sprintf(programStats.movelist, "%s (only move)", buf1);
8063                 programStats.depth = 1;
8064                 programStats.nr_moves = 1;
8065                 programStats.moves_left = 1;
8066                 programStats.nodes = 1;
8067                 programStats.time = 1;
8068                 programStats.got_only_move = 1;
8069
8070                 /* Not really, but we also use this member to
8071                    mean "line isn't going to change" (Crafty
8072                    isn't searching, so stats won't change) */
8073                 programStats.line_is_book = 1;
8074
8075                 SendProgramStatsToFrontend( cps, &programStats );
8076
8077                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8078                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8079                     DisplayMove(currentMove - 1);
8080                 }
8081                 return;
8082             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8083                               &time, &nodes, &plylev, &mvleft,
8084                               &mvtot, mvname) >= 5) {
8085                 /* The stat01: line is from Crafty (9.29+) in response
8086                    to the "." command */
8087                 programStats.seen_stat = 1;
8088                 cps->maybeThinking = TRUE;
8089
8090                 if (programStats.got_only_move || !appData.periodicUpdates)
8091                   return;
8092
8093                 programStats.depth = plylev;
8094                 programStats.time = time;
8095                 programStats.nodes = nodes;
8096                 programStats.moves_left = mvleft;
8097                 programStats.nr_moves = mvtot;
8098                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8099                 programStats.ok_to_send = 1;
8100                 programStats.movelist[0] = '\0';
8101
8102                 SendProgramStatsToFrontend( cps, &programStats );
8103
8104                 return;
8105
8106             } else if (strncmp(message,"++",2) == 0) {
8107                 /* Crafty 9.29+ outputs this */
8108                 programStats.got_fail = 2;
8109                 return;
8110
8111             } else if (strncmp(message,"--",2) == 0) {
8112                 /* Crafty 9.29+ outputs this */
8113                 programStats.got_fail = 1;
8114                 return;
8115
8116             } else if (thinkOutput[0] != NULLCHAR &&
8117                        strncmp(message, "    ", 4) == 0) {
8118                 unsigned message_len;
8119
8120                 p = message;
8121                 while (*p && *p == ' ') p++;
8122
8123                 message_len = strlen( p );
8124
8125                 /* [AS] Avoid buffer overflow */
8126                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8127                     strcat(thinkOutput, " ");
8128                     strcat(thinkOutput, p);
8129                 }
8130
8131                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8132                     strcat(programStats.movelist, " ");
8133                     strcat(programStats.movelist, p);
8134                 }
8135
8136                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8137                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8138                     DisplayMove(currentMove - 1);
8139                 }
8140                 return;
8141             }
8142         }
8143         else {
8144             buf1[0] = NULLCHAR;
8145
8146             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8147                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8148             {
8149                 ChessProgramStats cpstats;
8150
8151                 if (plyext != ' ' && plyext != '\t') {
8152                     time *= 100;
8153                 }
8154
8155                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8156                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8157                     curscore = -curscore;
8158                 }
8159
8160                 cpstats.depth = plylev;
8161                 cpstats.nodes = nodes;
8162                 cpstats.time = time;
8163                 cpstats.score = curscore;
8164                 cpstats.got_only_move = 0;
8165                 cpstats.movelist[0] = '\0';
8166
8167                 if (buf1[0] != NULLCHAR) {
8168                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8169                 }
8170
8171                 cpstats.ok_to_send = 0;
8172                 cpstats.line_is_book = 0;
8173                 cpstats.nr_moves = 0;
8174                 cpstats.moves_left = 0;
8175
8176                 SendProgramStatsToFrontend( cps, &cpstats );
8177             }
8178         }
8179     }
8180 }
8181
8182
8183 /* Parse a game score from the character string "game", and
8184    record it as the history of the current game.  The game
8185    score is NOT assumed to start from the standard position.
8186    The display is not updated in any way.
8187    */
8188 void
8189 ParseGameHistory(game)
8190      char *game;
8191 {
8192     ChessMove moveType;
8193     int fromX, fromY, toX, toY, boardIndex;
8194     char promoChar;
8195     char *p, *q;
8196     char buf[MSG_SIZ];
8197
8198     if (appData.debugMode)
8199       fprintf(debugFP, "Parsing game history: %s\n", game);
8200
8201     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8202     gameInfo.site = StrSave(appData.icsHost);
8203     gameInfo.date = PGNDate();
8204     gameInfo.round = StrSave("-");
8205
8206     /* Parse out names of players */
8207     while (*game == ' ') game++;
8208     p = buf;
8209     while (*game != ' ') *p++ = *game++;
8210     *p = NULLCHAR;
8211     gameInfo.white = StrSave(buf);
8212     while (*game == ' ') game++;
8213     p = buf;
8214     while (*game != ' ' && *game != '\n') *p++ = *game++;
8215     *p = NULLCHAR;
8216     gameInfo.black = StrSave(buf);
8217
8218     /* Parse moves */
8219     boardIndex = blackPlaysFirst ? 1 : 0;
8220     yynewstr(game);
8221     for (;;) {
8222         yyboardindex = boardIndex;
8223         moveType = (ChessMove) Myylex();
8224         switch (moveType) {
8225           case IllegalMove:             /* maybe suicide chess, etc. */
8226   if (appData.debugMode) {
8227     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8228     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8229     setbuf(debugFP, NULL);
8230   }
8231           case WhitePromotion:
8232           case BlackPromotion:
8233           case WhiteNonPromotion:
8234           case BlackNonPromotion:
8235           case NormalMove:
8236           case WhiteCapturesEnPassant:
8237           case BlackCapturesEnPassant:
8238           case WhiteKingSideCastle:
8239           case WhiteQueenSideCastle:
8240           case BlackKingSideCastle:
8241           case BlackQueenSideCastle:
8242           case WhiteKingSideCastleWild:
8243           case WhiteQueenSideCastleWild:
8244           case BlackKingSideCastleWild:
8245           case BlackQueenSideCastleWild:
8246           /* PUSH Fabien */
8247           case WhiteHSideCastleFR:
8248           case WhiteASideCastleFR:
8249           case BlackHSideCastleFR:
8250           case BlackASideCastleFR:
8251           /* POP Fabien */
8252             fromX = currentMoveString[0] - AAA;
8253             fromY = currentMoveString[1] - ONE;
8254             toX = currentMoveString[2] - AAA;
8255             toY = currentMoveString[3] - ONE;
8256             promoChar = currentMoveString[4];
8257             break;
8258           case WhiteDrop:
8259           case BlackDrop:
8260             fromX = moveType == WhiteDrop ?
8261               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8262             (int) CharToPiece(ToLower(currentMoveString[0]));
8263             fromY = DROP_RANK;
8264             toX = currentMoveString[2] - AAA;
8265             toY = currentMoveString[3] - ONE;
8266             promoChar = NULLCHAR;
8267             break;
8268           case AmbiguousMove:
8269             /* bug? */
8270             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8271   if (appData.debugMode) {
8272     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8273     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8274     setbuf(debugFP, NULL);
8275   }
8276             DisplayError(buf, 0);
8277             return;
8278           case ImpossibleMove:
8279             /* bug? */
8280             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8281   if (appData.debugMode) {
8282     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8283     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8284     setbuf(debugFP, NULL);
8285   }
8286             DisplayError(buf, 0);
8287             return;
8288           case EndOfFile:
8289             if (boardIndex < backwardMostMove) {
8290                 /* Oops, gap.  How did that happen? */
8291                 DisplayError(_("Gap in move list"), 0);
8292                 return;
8293             }
8294             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8295             if (boardIndex > forwardMostMove) {
8296                 forwardMostMove = boardIndex;
8297             }
8298             return;
8299           case ElapsedTime:
8300             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8301                 strcat(parseList[boardIndex-1], " ");
8302                 strcat(parseList[boardIndex-1], yy_text);
8303             }
8304             continue;
8305           case Comment:
8306           case PGNTag:
8307           case NAG:
8308           default:
8309             /* ignore */
8310             continue;
8311           case WhiteWins:
8312           case BlackWins:
8313           case GameIsDrawn:
8314           case GameUnfinished:
8315             if (gameMode == IcsExamining) {
8316                 if (boardIndex < backwardMostMove) {
8317                     /* Oops, gap.  How did that happen? */
8318                     return;
8319                 }
8320                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8321                 return;
8322             }
8323             gameInfo.result = moveType;
8324             p = strchr(yy_text, '{');
8325             if (p == NULL) p = strchr(yy_text, '(');
8326             if (p == NULL) {
8327                 p = yy_text;
8328                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8329             } else {
8330                 q = strchr(p, *p == '{' ? '}' : ')');
8331                 if (q != NULL) *q = NULLCHAR;
8332                 p++;
8333             }
8334             gameInfo.resultDetails = StrSave(p);
8335             continue;
8336         }
8337         if (boardIndex >= forwardMostMove &&
8338             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8339             backwardMostMove = blackPlaysFirst ? 1 : 0;
8340             return;
8341         }
8342         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8343                                  fromY, fromX, toY, toX, promoChar,
8344                                  parseList[boardIndex]);
8345         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8346         /* currentMoveString is set as a side-effect of yylex */
8347         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8348         strcat(moveList[boardIndex], "\n");
8349         boardIndex++;
8350         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8351         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8352           case MT_NONE:
8353           case MT_STALEMATE:
8354           default:
8355             break;
8356           case MT_CHECK:
8357             if(gameInfo.variant != VariantShogi)
8358                 strcat(parseList[boardIndex - 1], "+");
8359             break;
8360           case MT_CHECKMATE:
8361           case MT_STAINMATE:
8362             strcat(parseList[boardIndex - 1], "#");
8363             break;
8364         }
8365     }
8366 }
8367
8368
8369 /* Apply a move to the given board  */
8370 void
8371 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8372      int fromX, fromY, toX, toY;
8373      int promoChar;
8374      Board board;
8375 {
8376   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8377   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8378
8379     /* [HGM] compute & store e.p. status and castling rights for new position */
8380     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8381
8382       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8383       oldEP = (signed char)board[EP_STATUS];
8384       board[EP_STATUS] = EP_NONE;
8385
8386       if( board[toY][toX] != EmptySquare )
8387            board[EP_STATUS] = EP_CAPTURE;
8388
8389   if (fromY == DROP_RANK) {
8390         /* must be first */
8391         piece = board[toY][toX] = (ChessSquare) fromX;
8392   } else {
8393       int i;
8394
8395       if( board[fromY][fromX] == WhitePawn ) {
8396            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8397                board[EP_STATUS] = EP_PAWN_MOVE;
8398            if( toY-fromY==2) {
8399                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8400                         gameInfo.variant != VariantBerolina || toX < fromX)
8401                       board[EP_STATUS] = toX | berolina;
8402                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8403                         gameInfo.variant != VariantBerolina || toX > fromX)
8404                       board[EP_STATUS] = toX;
8405            }
8406       } else
8407       if( board[fromY][fromX] == BlackPawn ) {
8408            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8409                board[EP_STATUS] = EP_PAWN_MOVE;
8410            if( toY-fromY== -2) {
8411                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8412                         gameInfo.variant != VariantBerolina || toX < fromX)
8413                       board[EP_STATUS] = toX | berolina;
8414                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8415                         gameInfo.variant != VariantBerolina || toX > fromX)
8416                       board[EP_STATUS] = toX;
8417            }
8418        }
8419
8420        for(i=0; i<nrCastlingRights; i++) {
8421            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8422               board[CASTLING][i] == toX   && castlingRank[i] == toY
8423              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8424        }
8425
8426      if (fromX == toX && fromY == toY) return;
8427
8428      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8429      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8430      if(gameInfo.variant == VariantKnightmate)
8431          king += (int) WhiteUnicorn - (int) WhiteKing;
8432
8433     /* Code added by Tord: */
8434     /* FRC castling assumed when king captures friendly rook. */
8435     if (board[fromY][fromX] == WhiteKing &&
8436              board[toY][toX] == WhiteRook) {
8437       board[fromY][fromX] = EmptySquare;
8438       board[toY][toX] = EmptySquare;
8439       if(toX > fromX) {
8440         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8441       } else {
8442         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8443       }
8444     } else if (board[fromY][fromX] == BlackKing &&
8445                board[toY][toX] == BlackRook) {
8446       board[fromY][fromX] = EmptySquare;
8447       board[toY][toX] = EmptySquare;
8448       if(toX > fromX) {
8449         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8450       } else {
8451         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8452       }
8453     /* End of code added by Tord */
8454
8455     } else if (board[fromY][fromX] == king
8456         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8457         && toY == fromY && toX > fromX+1) {
8458         board[fromY][fromX] = EmptySquare;
8459         board[toY][toX] = king;
8460         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8461         board[fromY][BOARD_RGHT-1] = EmptySquare;
8462     } else if (board[fromY][fromX] == king
8463         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8464                && toY == fromY && toX < fromX-1) {
8465         board[fromY][fromX] = EmptySquare;
8466         board[toY][toX] = king;
8467         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8468         board[fromY][BOARD_LEFT] = EmptySquare;
8469     } else if (board[fromY][fromX] == WhitePawn
8470                && toY >= BOARD_HEIGHT-promoRank
8471                && gameInfo.variant != VariantXiangqi
8472                ) {
8473         /* white pawn promotion */
8474         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8475         if (board[toY][toX] == EmptySquare) {
8476             board[toY][toX] = WhiteQueen;
8477         }
8478         if(gameInfo.variant==VariantBughouse ||
8479            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8480             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8481         board[fromY][fromX] = EmptySquare;
8482     } else if ((fromY == BOARD_HEIGHT-4)
8483                && (toX != fromX)
8484                && gameInfo.variant != VariantXiangqi
8485                && gameInfo.variant != VariantBerolina
8486                && (board[fromY][fromX] == WhitePawn)
8487                && (board[toY][toX] == EmptySquare)) {
8488         board[fromY][fromX] = EmptySquare;
8489         board[toY][toX] = WhitePawn;
8490         captured = board[toY - 1][toX];
8491         board[toY - 1][toX] = EmptySquare;
8492     } else if ((fromY == BOARD_HEIGHT-4)
8493                && (toX == fromX)
8494                && gameInfo.variant == VariantBerolina
8495                && (board[fromY][fromX] == WhitePawn)
8496                && (board[toY][toX] == EmptySquare)) {
8497         board[fromY][fromX] = EmptySquare;
8498         board[toY][toX] = WhitePawn;
8499         if(oldEP & EP_BEROLIN_A) {
8500                 captured = board[fromY][fromX-1];
8501                 board[fromY][fromX-1] = EmptySquare;
8502         }else{  captured = board[fromY][fromX+1];
8503                 board[fromY][fromX+1] = EmptySquare;
8504         }
8505     } else if (board[fromY][fromX] == king
8506         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8507                && toY == fromY && toX > fromX+1) {
8508         board[fromY][fromX] = EmptySquare;
8509         board[toY][toX] = king;
8510         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8511         board[fromY][BOARD_RGHT-1] = EmptySquare;
8512     } else if (board[fromY][fromX] == king
8513         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8514                && toY == fromY && toX < fromX-1) {
8515         board[fromY][fromX] = EmptySquare;
8516         board[toY][toX] = king;
8517         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8518         board[fromY][BOARD_LEFT] = EmptySquare;
8519     } else if (fromY == 7 && fromX == 3
8520                && board[fromY][fromX] == BlackKing
8521                && toY == 7 && toX == 5) {
8522         board[fromY][fromX] = EmptySquare;
8523         board[toY][toX] = BlackKing;
8524         board[fromY][7] = EmptySquare;
8525         board[toY][4] = BlackRook;
8526     } else if (fromY == 7 && fromX == 3
8527                && board[fromY][fromX] == BlackKing
8528                && toY == 7 && toX == 1) {
8529         board[fromY][fromX] = EmptySquare;
8530         board[toY][toX] = BlackKing;
8531         board[fromY][0] = EmptySquare;
8532         board[toY][2] = BlackRook;
8533     } else if (board[fromY][fromX] == BlackPawn
8534                && toY < promoRank
8535                && gameInfo.variant != VariantXiangqi
8536                ) {
8537         /* black pawn promotion */
8538         board[toY][toX] = CharToPiece(ToLower(promoChar));
8539         if (board[toY][toX] == EmptySquare) {
8540             board[toY][toX] = BlackQueen;
8541         }
8542         if(gameInfo.variant==VariantBughouse ||
8543            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8544             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8545         board[fromY][fromX] = EmptySquare;
8546     } else if ((fromY == 3)
8547                && (toX != fromX)
8548                && gameInfo.variant != VariantXiangqi
8549                && gameInfo.variant != VariantBerolina
8550                && (board[fromY][fromX] == BlackPawn)
8551                && (board[toY][toX] == EmptySquare)) {
8552         board[fromY][fromX] = EmptySquare;
8553         board[toY][toX] = BlackPawn;
8554         captured = board[toY + 1][toX];
8555         board[toY + 1][toX] = EmptySquare;
8556     } else if ((fromY == 3)
8557                && (toX == fromX)
8558                && gameInfo.variant == VariantBerolina
8559                && (board[fromY][fromX] == BlackPawn)
8560                && (board[toY][toX] == EmptySquare)) {
8561         board[fromY][fromX] = EmptySquare;
8562         board[toY][toX] = BlackPawn;
8563         if(oldEP & EP_BEROLIN_A) {
8564                 captured = board[fromY][fromX-1];
8565                 board[fromY][fromX-1] = EmptySquare;
8566         }else{  captured = board[fromY][fromX+1];
8567                 board[fromY][fromX+1] = EmptySquare;
8568         }
8569     } else {
8570         board[toY][toX] = board[fromY][fromX];
8571         board[fromY][fromX] = EmptySquare;
8572     }
8573   }
8574
8575     if (gameInfo.holdingsWidth != 0) {
8576
8577       /* !!A lot more code needs to be written to support holdings  */
8578       /* [HGM] OK, so I have written it. Holdings are stored in the */
8579       /* penultimate board files, so they are automaticlly stored   */
8580       /* in the game history.                                       */
8581       if (fromY == DROP_RANK) {
8582         /* Delete from holdings, by decreasing count */
8583         /* and erasing image if necessary            */
8584         p = (int) fromX;
8585         if(p < (int) BlackPawn) { /* white drop */
8586              p -= (int)WhitePawn;
8587                  p = PieceToNumber((ChessSquare)p);
8588              if(p >= gameInfo.holdingsSize) p = 0;
8589              if(--board[p][BOARD_WIDTH-2] <= 0)
8590                   board[p][BOARD_WIDTH-1] = EmptySquare;
8591              if((int)board[p][BOARD_WIDTH-2] < 0)
8592                         board[p][BOARD_WIDTH-2] = 0;
8593         } else {                  /* black drop */
8594              p -= (int)BlackPawn;
8595                  p = PieceToNumber((ChessSquare)p);
8596              if(p >= gameInfo.holdingsSize) p = 0;
8597              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8598                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8599              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8600                         board[BOARD_HEIGHT-1-p][1] = 0;
8601         }
8602       }
8603       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8604           && gameInfo.variant != VariantBughouse        ) {
8605         /* [HGM] holdings: Add to holdings, if holdings exist */
8606         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8607                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8608                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8609         }
8610         p = (int) captured;
8611         if (p >= (int) BlackPawn) {
8612           p -= (int)BlackPawn;
8613           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8614                   /* in Shogi restore piece to its original  first */
8615                   captured = (ChessSquare) (DEMOTED captured);
8616                   p = DEMOTED p;
8617           }
8618           p = PieceToNumber((ChessSquare)p);
8619           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8620           board[p][BOARD_WIDTH-2]++;
8621           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8622         } else {
8623           p -= (int)WhitePawn;
8624           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8625                   captured = (ChessSquare) (DEMOTED captured);
8626                   p = DEMOTED p;
8627           }
8628           p = PieceToNumber((ChessSquare)p);
8629           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8630           board[BOARD_HEIGHT-1-p][1]++;
8631           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8632         }
8633       }
8634     } else if (gameInfo.variant == VariantAtomic) {
8635       if (captured != EmptySquare) {
8636         int y, x;
8637         for (y = toY-1; y <= toY+1; y++) {
8638           for (x = toX-1; x <= toX+1; x++) {
8639             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8640                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8641               board[y][x] = EmptySquare;
8642             }
8643           }
8644         }
8645         board[toY][toX] = EmptySquare;
8646       }
8647     }
8648     if(promoChar == '+') {
8649         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8650         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8651     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8652         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8653     }
8654     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8655                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8656         // [HGM] superchess: take promotion piece out of holdings
8657         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8658         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8659             if(!--board[k][BOARD_WIDTH-2])
8660                 board[k][BOARD_WIDTH-1] = EmptySquare;
8661         } else {
8662             if(!--board[BOARD_HEIGHT-1-k][1])
8663                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8664         }
8665     }
8666
8667 }
8668
8669 /* Updates forwardMostMove */
8670 void
8671 MakeMove(fromX, fromY, toX, toY, promoChar)
8672      int fromX, fromY, toX, toY;
8673      int promoChar;
8674 {
8675 //    forwardMostMove++; // [HGM] bare: moved downstream
8676
8677     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8678         int timeLeft; static int lastLoadFlag=0; int king, piece;
8679         piece = boards[forwardMostMove][fromY][fromX];
8680         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8681         if(gameInfo.variant == VariantKnightmate)
8682             king += (int) WhiteUnicorn - (int) WhiteKing;
8683         if(forwardMostMove == 0) {
8684             if(blackPlaysFirst)
8685                 fprintf(serverMoves, "%s;", second.tidy);
8686             fprintf(serverMoves, "%s;", first.tidy);
8687             if(!blackPlaysFirst)
8688                 fprintf(serverMoves, "%s;", second.tidy);
8689         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8690         lastLoadFlag = loadFlag;
8691         // print base move
8692         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8693         // print castling suffix
8694         if( toY == fromY && piece == king ) {
8695             if(toX-fromX > 1)
8696                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8697             if(fromX-toX >1)
8698                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8699         }
8700         // e.p. suffix
8701         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8702              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8703              boards[forwardMostMove][toY][toX] == EmptySquare
8704              && fromX != toX && fromY != toY)
8705                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8706         // promotion suffix
8707         if(promoChar != NULLCHAR)
8708                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8709         if(!loadFlag) {
8710             fprintf(serverMoves, "/%d/%d",
8711                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8712             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8713             else                      timeLeft = blackTimeRemaining/1000;
8714             fprintf(serverMoves, "/%d", timeLeft);
8715         }
8716         fflush(serverMoves);
8717     }
8718
8719     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8720       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8721                         0, 1);
8722       return;
8723     }
8724     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8725     if (commentList[forwardMostMove+1] != NULL) {
8726         free(commentList[forwardMostMove+1]);
8727         commentList[forwardMostMove+1] = NULL;
8728     }
8729     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8730     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8731     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8732     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8733     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8734     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8735     gameInfo.result = GameUnfinished;
8736     if (gameInfo.resultDetails != NULL) {
8737         free(gameInfo.resultDetails);
8738         gameInfo.resultDetails = NULL;
8739     }
8740     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8741                               moveList[forwardMostMove - 1]);
8742     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8743                              PosFlags(forwardMostMove - 1),
8744                              fromY, fromX, toY, toX, promoChar,
8745                              parseList[forwardMostMove - 1]);
8746     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8747       case MT_NONE:
8748       case MT_STALEMATE:
8749       default:
8750         break;
8751       case MT_CHECK:
8752         if(gameInfo.variant != VariantShogi)
8753             strcat(parseList[forwardMostMove - 1], "+");
8754         break;
8755       case MT_CHECKMATE:
8756       case MT_STAINMATE:
8757         strcat(parseList[forwardMostMove - 1], "#");
8758         break;
8759     }
8760     if (appData.debugMode) {
8761         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8762     }
8763
8764 }
8765
8766 /* Updates currentMove if not pausing */
8767 void
8768 ShowMove(fromX, fromY, toX, toY)
8769 {
8770     int instant = (gameMode == PlayFromGameFile) ?
8771         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8772     if(appData.noGUI) return;
8773     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8774         if (!instant) {
8775             if (forwardMostMove == currentMove + 1) {
8776                 AnimateMove(boards[forwardMostMove - 1],
8777                             fromX, fromY, toX, toY);
8778             }
8779             if (appData.highlightLastMove) {
8780                 SetHighlights(fromX, fromY, toX, toY);
8781             }
8782         }
8783         currentMove = forwardMostMove;
8784     }
8785
8786     if (instant) return;
8787
8788     DisplayMove(currentMove - 1);
8789     DrawPosition(FALSE, boards[currentMove]);
8790     DisplayBothClocks();
8791     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8792 }
8793
8794 void SendEgtPath(ChessProgramState *cps)
8795 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8796         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8797
8798         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8799
8800         while(*p) {
8801             char c, *q = name+1, *r, *s;
8802
8803             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8804             while(*p && *p != ',') *q++ = *p++;
8805             *q++ = ':'; *q = 0;
8806             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8807                 strcmp(name, ",nalimov:") == 0 ) {
8808                 // take nalimov path from the menu-changeable option first, if it is defined
8809               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8810                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8811             } else
8812             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8813                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8814                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8815                 s = r = StrStr(s, ":") + 1; // beginning of path info
8816                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8817                 c = *r; *r = 0;             // temporarily null-terminate path info
8818                     *--q = 0;               // strip of trailig ':' from name
8819                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8820                 *r = c;
8821                 SendToProgram(buf,cps);     // send egtbpath command for this format
8822             }
8823             if(*p == ',') p++; // read away comma to position for next format name
8824         }
8825 }
8826
8827 void
8828 InitChessProgram(cps, setup)
8829      ChessProgramState *cps;
8830      int setup; /* [HGM] needed to setup FRC opening position */
8831 {
8832     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8833     if (appData.noChessProgram) return;
8834     hintRequested = FALSE;
8835     bookRequested = FALSE;
8836
8837     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8838     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8839     if(cps->memSize) { /* [HGM] memory */
8840       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8841         SendToProgram(buf, cps);
8842     }
8843     SendEgtPath(cps); /* [HGM] EGT */
8844     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8845       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8846         SendToProgram(buf, cps);
8847     }
8848
8849     SendToProgram(cps->initString, cps);
8850     if (gameInfo.variant != VariantNormal &&
8851         gameInfo.variant != VariantLoadable
8852         /* [HGM] also send variant if board size non-standard */
8853         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8854                                             ) {
8855       char *v = VariantName(gameInfo.variant);
8856       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8857         /* [HGM] in protocol 1 we have to assume all variants valid */
8858         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8859         DisplayFatalError(buf, 0, 1);
8860         return;
8861       }
8862
8863       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8864       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8865       if( gameInfo.variant == VariantXiangqi )
8866            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8867       if( gameInfo.variant == VariantShogi )
8868            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8869       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8870            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8871       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8872                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8873            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8874       if( gameInfo.variant == VariantCourier )
8875            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8876       if( gameInfo.variant == VariantSuper )
8877            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8878       if( gameInfo.variant == VariantGreat )
8879            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8880
8881       if(overruled) {
8882         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8883                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8884            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8885            if(StrStr(cps->variants, b) == NULL) {
8886                // specific sized variant not known, check if general sizing allowed
8887                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8888                    if(StrStr(cps->variants, "boardsize") == NULL) {
8889                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8890                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8891                        DisplayFatalError(buf, 0, 1);
8892                        return;
8893                    }
8894                    /* [HGM] here we really should compare with the maximum supported board size */
8895                }
8896            }
8897       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8898       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8899       SendToProgram(buf, cps);
8900     }
8901     currentlyInitializedVariant = gameInfo.variant;
8902
8903     /* [HGM] send opening position in FRC to first engine */
8904     if(setup) {
8905           SendToProgram("force\n", cps);
8906           SendBoard(cps, 0);
8907           /* engine is now in force mode! Set flag to wake it up after first move. */
8908           setboardSpoiledMachineBlack = 1;
8909     }
8910
8911     if (cps->sendICS) {
8912       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8913       SendToProgram(buf, cps);
8914     }
8915     cps->maybeThinking = FALSE;
8916     cps->offeredDraw = 0;
8917     if (!appData.icsActive) {
8918         SendTimeControl(cps, movesPerSession, timeControl,
8919                         timeIncrement, appData.searchDepth,
8920                         searchTime);
8921     }
8922     if (appData.showThinking
8923         // [HGM] thinking: four options require thinking output to be sent
8924         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8925                                 ) {
8926         SendToProgram("post\n", cps);
8927     }
8928     SendToProgram("hard\n", cps);
8929     if (!appData.ponderNextMove) {
8930         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8931            it without being sure what state we are in first.  "hard"
8932            is not a toggle, so that one is OK.
8933          */
8934         SendToProgram("easy\n", cps);
8935     }
8936     if (cps->usePing) {
8937       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8938       SendToProgram(buf, cps);
8939     }
8940     cps->initDone = TRUE;
8941 }
8942
8943
8944 void
8945 StartChessProgram(cps)
8946      ChessProgramState *cps;
8947 {
8948     char buf[MSG_SIZ];
8949     int err;
8950
8951     if (appData.noChessProgram) return;
8952     cps->initDone = FALSE;
8953
8954     if (strcmp(cps->host, "localhost") == 0) {
8955         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8956     } else if (*appData.remoteShell == NULLCHAR) {
8957         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8958     } else {
8959         if (*appData.remoteUser == NULLCHAR) {
8960           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8961                     cps->program);
8962         } else {
8963           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8964                     cps->host, appData.remoteUser, cps->program);
8965         }
8966         err = StartChildProcess(buf, "", &cps->pr);
8967     }
8968
8969     if (err != 0) {
8970       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8971         DisplayFatalError(buf, err, 1);
8972         cps->pr = NoProc;
8973         cps->isr = NULL;
8974         return;
8975     }
8976
8977     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8978     if (cps->protocolVersion > 1) {
8979       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8980       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8981       cps->comboCnt = 0;  //                and values of combo boxes
8982       SendToProgram(buf, cps);
8983     } else {
8984       SendToProgram("xboard\n", cps);
8985     }
8986 }
8987
8988
8989 void
8990 TwoMachinesEventIfReady P((void))
8991 {
8992   if (first.lastPing != first.lastPong) {
8993     DisplayMessage("", _("Waiting for first chess program"));
8994     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8995     return;
8996   }
8997   if (second.lastPing != second.lastPong) {
8998     DisplayMessage("", _("Waiting for second chess program"));
8999     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9000     return;
9001   }
9002   ThawUI();
9003   TwoMachinesEvent();
9004 }
9005
9006 void
9007 NextMatchGame P((void))
9008 {
9009     int index; /* [HGM] autoinc: step load index during match */
9010     Reset(FALSE, TRUE);
9011     if (*appData.loadGameFile != NULLCHAR) {
9012         index = appData.loadGameIndex;
9013         if(index < 0) { // [HGM] autoinc
9014             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9015             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9016         }
9017         LoadGameFromFile(appData.loadGameFile,
9018                          index,
9019                          appData.loadGameFile, FALSE);
9020     } else if (*appData.loadPositionFile != NULLCHAR) {
9021         index = appData.loadPositionIndex;
9022         if(index < 0) { // [HGM] autoinc
9023             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9024             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9025         }
9026         LoadPositionFromFile(appData.loadPositionFile,
9027                              index,
9028                              appData.loadPositionFile);
9029     }
9030     TwoMachinesEventIfReady();
9031 }
9032
9033 void UserAdjudicationEvent( int result )
9034 {
9035     ChessMove gameResult = GameIsDrawn;
9036
9037     if( result > 0 ) {
9038         gameResult = WhiteWins;
9039     }
9040     else if( result < 0 ) {
9041         gameResult = BlackWins;
9042     }
9043
9044     if( gameMode == TwoMachinesPlay ) {
9045         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9046     }
9047 }
9048
9049
9050 // [HGM] save: calculate checksum of game to make games easily identifiable
9051 int StringCheckSum(char *s)
9052 {
9053         int i = 0;
9054         if(s==NULL) return 0;
9055         while(*s) i = i*259 + *s++;
9056         return i;
9057 }
9058
9059 int GameCheckSum()
9060 {
9061         int i, sum=0;
9062         for(i=backwardMostMove; i<forwardMostMove; i++) {
9063                 sum += pvInfoList[i].depth;
9064                 sum += StringCheckSum(parseList[i]);
9065                 sum += StringCheckSum(commentList[i]);
9066                 sum *= 261;
9067         }
9068         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9069         return sum + StringCheckSum(commentList[i]);
9070 } // end of save patch
9071
9072 void
9073 GameEnds(result, resultDetails, whosays)
9074      ChessMove result;
9075      char *resultDetails;
9076      int whosays;
9077 {
9078     GameMode nextGameMode;
9079     int isIcsGame;
9080     char buf[MSG_SIZ], popupRequested = 0;
9081
9082     if(endingGame) return; /* [HGM] crash: forbid recursion */
9083     endingGame = 1;
9084     if(twoBoards) { // [HGM] dual: switch back to one board
9085         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9086         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9087     }
9088     if (appData.debugMode) {
9089       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9090               result, resultDetails ? resultDetails : "(null)", whosays);
9091     }
9092
9093     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9094
9095     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9096         /* If we are playing on ICS, the server decides when the
9097            game is over, but the engine can offer to draw, claim
9098            a draw, or resign.
9099          */
9100 #if ZIPPY
9101         if (appData.zippyPlay && first.initDone) {
9102             if (result == GameIsDrawn) {
9103                 /* In case draw still needs to be claimed */
9104                 SendToICS(ics_prefix);
9105                 SendToICS("draw\n");
9106             } else if (StrCaseStr(resultDetails, "resign")) {
9107                 SendToICS(ics_prefix);
9108                 SendToICS("resign\n");
9109             }
9110         }
9111 #endif
9112         endingGame = 0; /* [HGM] crash */
9113         return;
9114     }
9115
9116     /* If we're loading the game from a file, stop */
9117     if (whosays == GE_FILE) {
9118       (void) StopLoadGameTimer();
9119       gameFileFP = NULL;
9120     }
9121
9122     /* Cancel draw offers */
9123     first.offeredDraw = second.offeredDraw = 0;
9124
9125     /* If this is an ICS game, only ICS can really say it's done;
9126        if not, anyone can. */
9127     isIcsGame = (gameMode == IcsPlayingWhite ||
9128                  gameMode == IcsPlayingBlack ||
9129                  gameMode == IcsObserving    ||
9130                  gameMode == IcsExamining);
9131
9132     if (!isIcsGame || whosays == GE_ICS) {
9133         /* OK -- not an ICS game, or ICS said it was done */
9134         StopClocks();
9135         if (!isIcsGame && !appData.noChessProgram)
9136           SetUserThinkingEnables();
9137
9138         /* [HGM] if a machine claims the game end we verify this claim */
9139         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9140             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9141                 char claimer;
9142                 ChessMove trueResult = (ChessMove) -1;
9143
9144                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9145                                             first.twoMachinesColor[0] :
9146                                             second.twoMachinesColor[0] ;
9147
9148                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9149                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9150                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9151                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9152                 } else
9153                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9154                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9155                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9156                 } else
9157                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9158                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9159                 }
9160
9161                 // now verify win claims, but not in drop games, as we don't understand those yet
9162                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9163                                                  || gameInfo.variant == VariantGreat) &&
9164                     (result == WhiteWins && claimer == 'w' ||
9165                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9166                       if (appData.debugMode) {
9167                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9168                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9169                       }
9170                       if(result != trueResult) {
9171                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9172                               result = claimer == 'w' ? BlackWins : WhiteWins;
9173                               resultDetails = buf;
9174                       }
9175                 } else
9176                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9177                     && (forwardMostMove <= backwardMostMove ||
9178                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9179                         (claimer=='b')==(forwardMostMove&1))
9180                                                                                   ) {
9181                       /* [HGM] verify: draws that were not flagged are false claims */
9182                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9183                       result = claimer == 'w' ? BlackWins : WhiteWins;
9184                       resultDetails = buf;
9185                 }
9186                 /* (Claiming a loss is accepted no questions asked!) */
9187             }
9188             /* [HGM] bare: don't allow bare King to win */
9189             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9190                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9191                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9192                && result != GameIsDrawn)
9193             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9194                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9195                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9196                         if(p >= 0 && p <= (int)WhiteKing) k++;
9197                 }
9198                 if (appData.debugMode) {
9199                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9200                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9201                 }
9202                 if(k <= 1) {
9203                         result = GameIsDrawn;
9204                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9205                         resultDetails = buf;
9206                 }
9207             }
9208         }
9209
9210
9211         if(serverMoves != NULL && !loadFlag) { char c = '=';
9212             if(result==WhiteWins) c = '+';
9213             if(result==BlackWins) c = '-';
9214             if(resultDetails != NULL)
9215                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9216         }
9217         if (resultDetails != NULL) {
9218             gameInfo.result = result;
9219             gameInfo.resultDetails = StrSave(resultDetails);
9220
9221             /* display last move only if game was not loaded from file */
9222             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9223                 DisplayMove(currentMove - 1);
9224
9225             if (forwardMostMove != 0) {
9226                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9227                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9228                                                                 ) {
9229                     if (*appData.saveGameFile != NULLCHAR) {
9230                         SaveGameToFile(appData.saveGameFile, TRUE);
9231                     } else if (appData.autoSaveGames) {
9232                         AutoSaveGame();
9233                     }
9234                     if (*appData.savePositionFile != NULLCHAR) {
9235                         SavePositionToFile(appData.savePositionFile);
9236                     }
9237                 }
9238             }
9239
9240             /* Tell program how game ended in case it is learning */
9241             /* [HGM] Moved this to after saving the PGN, just in case */
9242             /* engine died and we got here through time loss. In that */
9243             /* case we will get a fatal error writing the pipe, which */
9244             /* would otherwise lose us the PGN.                       */
9245             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9246             /* output during GameEnds should never be fatal anymore   */
9247             if (gameMode == MachinePlaysWhite ||
9248                 gameMode == MachinePlaysBlack ||
9249                 gameMode == TwoMachinesPlay ||
9250                 gameMode == IcsPlayingWhite ||
9251                 gameMode == IcsPlayingBlack ||
9252                 gameMode == BeginningOfGame) {
9253                 char buf[MSG_SIZ];
9254                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9255                         resultDetails);
9256                 if (first.pr != NoProc) {
9257                     SendToProgram(buf, &first);
9258                 }
9259                 if (second.pr != NoProc &&
9260                     gameMode == TwoMachinesPlay) {
9261                     SendToProgram(buf, &second);
9262                 }
9263             }
9264         }
9265
9266         if (appData.icsActive) {
9267             if (appData.quietPlay &&
9268                 (gameMode == IcsPlayingWhite ||
9269                  gameMode == IcsPlayingBlack)) {
9270                 SendToICS(ics_prefix);
9271                 SendToICS("set shout 1\n");
9272             }
9273             nextGameMode = IcsIdle;
9274             ics_user_moved = FALSE;
9275             /* clean up premove.  It's ugly when the game has ended and the
9276              * premove highlights are still on the board.
9277              */
9278             if (gotPremove) {
9279               gotPremove = FALSE;
9280               ClearPremoveHighlights();
9281               DrawPosition(FALSE, boards[currentMove]);
9282             }
9283             if (whosays == GE_ICS) {
9284                 switch (result) {
9285                 case WhiteWins:
9286                     if (gameMode == IcsPlayingWhite)
9287                         PlayIcsWinSound();
9288                     else if(gameMode == IcsPlayingBlack)
9289                         PlayIcsLossSound();
9290                     break;
9291                 case BlackWins:
9292                     if (gameMode == IcsPlayingBlack)
9293                         PlayIcsWinSound();
9294                     else if(gameMode == IcsPlayingWhite)
9295                         PlayIcsLossSound();
9296                     break;
9297                 case GameIsDrawn:
9298                     PlayIcsDrawSound();
9299                     break;
9300                 default:
9301                     PlayIcsUnfinishedSound();
9302                 }
9303             }
9304         } else if (gameMode == EditGame ||
9305                    gameMode == PlayFromGameFile ||
9306                    gameMode == AnalyzeMode ||
9307                    gameMode == AnalyzeFile) {
9308             nextGameMode = gameMode;
9309         } else {
9310             nextGameMode = EndOfGame;
9311         }
9312         pausing = FALSE;
9313         ModeHighlight();
9314     } else {
9315         nextGameMode = gameMode;
9316     }
9317
9318     if (appData.noChessProgram) {
9319         gameMode = nextGameMode;
9320         ModeHighlight();
9321         endingGame = 0; /* [HGM] crash */
9322         return;
9323     }
9324
9325     if (first.reuse) {
9326         /* Put first chess program into idle state */
9327         if (first.pr != NoProc &&
9328             (gameMode == MachinePlaysWhite ||
9329              gameMode == MachinePlaysBlack ||
9330              gameMode == TwoMachinesPlay ||
9331              gameMode == IcsPlayingWhite ||
9332              gameMode == IcsPlayingBlack ||
9333              gameMode == BeginningOfGame)) {
9334             SendToProgram("force\n", &first);
9335             if (first.usePing) {
9336               char buf[MSG_SIZ];
9337               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9338               SendToProgram(buf, &first);
9339             }
9340         }
9341     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9342         /* Kill off first chess program */
9343         if (first.isr != NULL)
9344           RemoveInputSource(first.isr);
9345         first.isr = NULL;
9346
9347         if (first.pr != NoProc) {
9348             ExitAnalyzeMode();
9349             DoSleep( appData.delayBeforeQuit );
9350             SendToProgram("quit\n", &first);
9351             DoSleep( appData.delayAfterQuit );
9352             DestroyChildProcess(first.pr, first.useSigterm);
9353         }
9354         first.pr = NoProc;
9355     }
9356     if (second.reuse) {
9357         /* Put second chess program into idle state */
9358         if (second.pr != NoProc &&
9359             gameMode == TwoMachinesPlay) {
9360             SendToProgram("force\n", &second);
9361             if (second.usePing) {
9362               char buf[MSG_SIZ];
9363               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9364               SendToProgram(buf, &second);
9365             }
9366         }
9367     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9368         /* Kill off second chess program */
9369         if (second.isr != NULL)
9370           RemoveInputSource(second.isr);
9371         second.isr = NULL;
9372
9373         if (second.pr != NoProc) {
9374             DoSleep( appData.delayBeforeQuit );
9375             SendToProgram("quit\n", &second);
9376             DoSleep( appData.delayAfterQuit );
9377             DestroyChildProcess(second.pr, second.useSigterm);
9378         }
9379         second.pr = NoProc;
9380     }
9381
9382     if (matchMode && gameMode == TwoMachinesPlay) {
9383         switch (result) {
9384         case WhiteWins:
9385           if (first.twoMachinesColor[0] == 'w') {
9386             first.matchWins++;
9387           } else {
9388             second.matchWins++;
9389           }
9390           break;
9391         case BlackWins:
9392           if (first.twoMachinesColor[0] == 'b') {
9393             first.matchWins++;
9394           } else {
9395             second.matchWins++;
9396           }
9397           break;
9398         default:
9399           break;
9400         }
9401         if (matchGame < appData.matchGames) {
9402             char *tmp;
9403             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9404                 tmp = first.twoMachinesColor;
9405                 first.twoMachinesColor = second.twoMachinesColor;
9406                 second.twoMachinesColor = tmp;
9407             }
9408             gameMode = nextGameMode;
9409             matchGame++;
9410             if(appData.matchPause>10000 || appData.matchPause<10)
9411                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9412             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9413             endingGame = 0; /* [HGM] crash */
9414             return;
9415         } else {
9416             gameMode = nextGameMode;
9417             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9418                      first.tidy, second.tidy,
9419                      first.matchWins, second.matchWins,
9420                      appData.matchGames - (first.matchWins + second.matchWins));
9421             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9422         }
9423     }
9424     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9425         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9426       ExitAnalyzeMode();
9427     gameMode = nextGameMode;
9428     ModeHighlight();
9429     endingGame = 0;  /* [HGM] crash */
9430     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9431       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9432         matchMode = FALSE; appData.matchGames = matchGame = 0;
9433         DisplayNote(buf);
9434       }
9435     }
9436 }
9437
9438 /* Assumes program was just initialized (initString sent).
9439    Leaves program in force mode. */
9440 void
9441 FeedMovesToProgram(cps, upto)
9442      ChessProgramState *cps;
9443      int upto;
9444 {
9445     int i;
9446
9447     if (appData.debugMode)
9448       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9449               startedFromSetupPosition ? "position and " : "",
9450               backwardMostMove, upto, cps->which);
9451     if(currentlyInitializedVariant != gameInfo.variant) {
9452       char buf[MSG_SIZ];
9453         // [HGM] variantswitch: make engine aware of new variant
9454         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9455                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9456         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9457         SendToProgram(buf, cps);
9458         currentlyInitializedVariant = gameInfo.variant;
9459     }
9460     SendToProgram("force\n", cps);
9461     if (startedFromSetupPosition) {
9462         SendBoard(cps, backwardMostMove);
9463     if (appData.debugMode) {
9464         fprintf(debugFP, "feedMoves\n");
9465     }
9466     }
9467     for (i = backwardMostMove; i < upto; i++) {
9468         SendMoveToProgram(i, cps);
9469     }
9470 }
9471
9472
9473 void
9474 ResurrectChessProgram()
9475 {
9476      /* The chess program may have exited.
9477         If so, restart it and feed it all the moves made so far. */
9478
9479     if (appData.noChessProgram || first.pr != NoProc) return;
9480
9481     StartChessProgram(&first);
9482     InitChessProgram(&first, FALSE);
9483     FeedMovesToProgram(&first, currentMove);
9484
9485     if (!first.sendTime) {
9486         /* can't tell gnuchess what its clock should read,
9487            so we bow to its notion. */
9488         ResetClocks();
9489         timeRemaining[0][currentMove] = whiteTimeRemaining;
9490         timeRemaining[1][currentMove] = blackTimeRemaining;
9491     }
9492
9493     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9494                 appData.icsEngineAnalyze) && first.analysisSupport) {
9495       SendToProgram("analyze\n", &first);
9496       first.analyzing = TRUE;
9497     }
9498 }
9499
9500 /*
9501  * Button procedures
9502  */
9503 void
9504 Reset(redraw, init)
9505      int redraw, init;
9506 {
9507     int i;
9508
9509     if (appData.debugMode) {
9510         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9511                 redraw, init, gameMode);
9512     }
9513     CleanupTail(); // [HGM] vari: delete any stored variations
9514     pausing = pauseExamInvalid = FALSE;
9515     startedFromSetupPosition = blackPlaysFirst = FALSE;
9516     firstMove = TRUE;
9517     whiteFlag = blackFlag = FALSE;
9518     userOfferedDraw = FALSE;
9519     hintRequested = bookRequested = FALSE;
9520     first.maybeThinking = FALSE;
9521     second.maybeThinking = FALSE;
9522     first.bookSuspend = FALSE; // [HGM] book
9523     second.bookSuspend = FALSE;
9524     thinkOutput[0] = NULLCHAR;
9525     lastHint[0] = NULLCHAR;
9526     ClearGameInfo(&gameInfo);
9527     gameInfo.variant = StringToVariant(appData.variant);
9528     ics_user_moved = ics_clock_paused = FALSE;
9529     ics_getting_history = H_FALSE;
9530     ics_gamenum = -1;
9531     white_holding[0] = black_holding[0] = NULLCHAR;
9532     ClearProgramStats();
9533     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9534
9535     ResetFrontEnd();
9536     ClearHighlights();
9537     flipView = appData.flipView;
9538     ClearPremoveHighlights();
9539     gotPremove = FALSE;
9540     alarmSounded = FALSE;
9541
9542     GameEnds(EndOfFile, NULL, GE_PLAYER);
9543     if(appData.serverMovesName != NULL) {
9544         /* [HGM] prepare to make moves file for broadcasting */
9545         clock_t t = clock();
9546         if(serverMoves != NULL) fclose(serverMoves);
9547         serverMoves = fopen(appData.serverMovesName, "r");
9548         if(serverMoves != NULL) {
9549             fclose(serverMoves);
9550             /* delay 15 sec before overwriting, so all clients can see end */
9551             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9552         }
9553         serverMoves = fopen(appData.serverMovesName, "w");
9554     }
9555
9556     ExitAnalyzeMode();
9557     gameMode = BeginningOfGame;
9558     ModeHighlight();
9559     if(appData.icsActive) gameInfo.variant = VariantNormal;
9560     currentMove = forwardMostMove = backwardMostMove = 0;
9561     InitPosition(redraw);
9562     for (i = 0; i < MAX_MOVES; i++) {
9563         if (commentList[i] != NULL) {
9564             free(commentList[i]);
9565             commentList[i] = NULL;
9566         }
9567     }
9568     ResetClocks();
9569     timeRemaining[0][0] = whiteTimeRemaining;
9570     timeRemaining[1][0] = blackTimeRemaining;
9571     if (first.pr == NULL) {
9572         StartChessProgram(&first);
9573     }
9574     if (init) {
9575             InitChessProgram(&first, startedFromSetupPosition);
9576     }
9577     DisplayTitle("");
9578     DisplayMessage("", "");
9579     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9580     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9581 }
9582
9583 void
9584 AutoPlayGameLoop()
9585 {
9586     for (;;) {
9587         if (!AutoPlayOneMove())
9588           return;
9589         if (matchMode || appData.timeDelay == 0)
9590           continue;
9591         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9592           return;
9593         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9594         break;
9595     }
9596 }
9597
9598
9599 int
9600 AutoPlayOneMove()
9601 {
9602     int fromX, fromY, toX, toY;
9603
9604     if (appData.debugMode) {
9605       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9606     }
9607
9608     if (gameMode != PlayFromGameFile)
9609       return FALSE;
9610
9611     if (currentMove >= forwardMostMove) {
9612       gameMode = EditGame;
9613       ModeHighlight();
9614
9615       /* [AS] Clear current move marker at the end of a game */
9616       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9617
9618       return FALSE;
9619     }
9620
9621     toX = moveList[currentMove][2] - AAA;
9622     toY = moveList[currentMove][3] - ONE;
9623
9624     if (moveList[currentMove][1] == '@') {
9625         if (appData.highlightLastMove) {
9626             SetHighlights(-1, -1, toX, toY);
9627         }
9628     } else {
9629         fromX = moveList[currentMove][0] - AAA;
9630         fromY = moveList[currentMove][1] - ONE;
9631
9632         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9633
9634         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9635
9636         if (appData.highlightLastMove) {
9637             SetHighlights(fromX, fromY, toX, toY);
9638         }
9639     }
9640     DisplayMove(currentMove);
9641     SendMoveToProgram(currentMove++, &first);
9642     DisplayBothClocks();
9643     DrawPosition(FALSE, boards[currentMove]);
9644     // [HGM] PV info: always display, routine tests if empty
9645     DisplayComment(currentMove - 1, commentList[currentMove]);
9646     return TRUE;
9647 }
9648
9649
9650 int
9651 LoadGameOneMove(readAhead)
9652      ChessMove readAhead;
9653 {
9654     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9655     char promoChar = NULLCHAR;
9656     ChessMove moveType;
9657     char move[MSG_SIZ];
9658     char *p, *q;
9659
9660     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9661         gameMode != AnalyzeMode && gameMode != Training) {
9662         gameFileFP = NULL;
9663         return FALSE;
9664     }
9665
9666     yyboardindex = forwardMostMove;
9667     if (readAhead != EndOfFile) {
9668       moveType = readAhead;
9669     } else {
9670       if (gameFileFP == NULL)
9671           return FALSE;
9672       moveType = (ChessMove) Myylex();
9673     }
9674
9675     done = FALSE;
9676     switch (moveType) {
9677       case Comment:
9678         if (appData.debugMode)
9679           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9680         p = yy_text;
9681
9682         /* append the comment but don't display it */
9683         AppendComment(currentMove, p, FALSE);
9684         return TRUE;
9685
9686       case WhiteCapturesEnPassant:
9687       case BlackCapturesEnPassant:
9688       case WhitePromotion:
9689       case BlackPromotion:
9690       case WhiteNonPromotion:
9691       case BlackNonPromotion:
9692       case NormalMove:
9693       case WhiteKingSideCastle:
9694       case WhiteQueenSideCastle:
9695       case BlackKingSideCastle:
9696       case BlackQueenSideCastle:
9697       case WhiteKingSideCastleWild:
9698       case WhiteQueenSideCastleWild:
9699       case BlackKingSideCastleWild:
9700       case BlackQueenSideCastleWild:
9701       /* PUSH Fabien */
9702       case WhiteHSideCastleFR:
9703       case WhiteASideCastleFR:
9704       case BlackHSideCastleFR:
9705       case BlackASideCastleFR:
9706       /* POP Fabien */
9707         if (appData.debugMode)
9708           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9709         fromX = currentMoveString[0] - AAA;
9710         fromY = currentMoveString[1] - ONE;
9711         toX = currentMoveString[2] - AAA;
9712         toY = currentMoveString[3] - ONE;
9713         promoChar = currentMoveString[4];
9714         break;
9715
9716       case WhiteDrop:
9717       case BlackDrop:
9718         if (appData.debugMode)
9719           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9720         fromX = moveType == WhiteDrop ?
9721           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9722         (int) CharToPiece(ToLower(currentMoveString[0]));
9723         fromY = DROP_RANK;
9724         toX = currentMoveString[2] - AAA;
9725         toY = currentMoveString[3] - ONE;
9726         break;
9727
9728       case WhiteWins:
9729       case BlackWins:
9730       case GameIsDrawn:
9731       case GameUnfinished:
9732         if (appData.debugMode)
9733           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9734         p = strchr(yy_text, '{');
9735         if (p == NULL) p = strchr(yy_text, '(');
9736         if (p == NULL) {
9737             p = yy_text;
9738             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9739         } else {
9740             q = strchr(p, *p == '{' ? '}' : ')');
9741             if (q != NULL) *q = NULLCHAR;
9742             p++;
9743         }
9744         GameEnds(moveType, p, GE_FILE);
9745         done = TRUE;
9746         if (cmailMsgLoaded) {
9747             ClearHighlights();
9748             flipView = WhiteOnMove(currentMove);
9749             if (moveType == GameUnfinished) flipView = !flipView;
9750             if (appData.debugMode)
9751               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9752         }
9753         break;
9754
9755       case EndOfFile:
9756         if (appData.debugMode)
9757           fprintf(debugFP, "Parser hit end of file\n");
9758         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9759           case MT_NONE:
9760           case MT_CHECK:
9761             break;
9762           case MT_CHECKMATE:
9763           case MT_STAINMATE:
9764             if (WhiteOnMove(currentMove)) {
9765                 GameEnds(BlackWins, "Black mates", GE_FILE);
9766             } else {
9767                 GameEnds(WhiteWins, "White mates", GE_FILE);
9768             }
9769             break;
9770           case MT_STALEMATE:
9771             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9772             break;
9773         }
9774         done = TRUE;
9775         break;
9776
9777       case MoveNumberOne:
9778         if (lastLoadGameStart == GNUChessGame) {
9779             /* GNUChessGames have numbers, but they aren't move numbers */
9780             if (appData.debugMode)
9781               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9782                       yy_text, (int) moveType);
9783             return LoadGameOneMove(EndOfFile); /* tail recursion */
9784         }
9785         /* else fall thru */
9786
9787       case XBoardGame:
9788       case GNUChessGame:
9789       case PGNTag:
9790         /* Reached start of next game in file */
9791         if (appData.debugMode)
9792           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9793         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9794           case MT_NONE:
9795           case MT_CHECK:
9796             break;
9797           case MT_CHECKMATE:
9798           case MT_STAINMATE:
9799             if (WhiteOnMove(currentMove)) {
9800                 GameEnds(BlackWins, "Black mates", GE_FILE);
9801             } else {
9802                 GameEnds(WhiteWins, "White mates", GE_FILE);
9803             }
9804             break;
9805           case MT_STALEMATE:
9806             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9807             break;
9808         }
9809         done = TRUE;
9810         break;
9811
9812       case PositionDiagram:     /* should not happen; ignore */
9813       case ElapsedTime:         /* ignore */
9814       case NAG:                 /* ignore */
9815         if (appData.debugMode)
9816           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9817                   yy_text, (int) moveType);
9818         return LoadGameOneMove(EndOfFile); /* tail recursion */
9819
9820       case IllegalMove:
9821         if (appData.testLegality) {
9822             if (appData.debugMode)
9823               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9824             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9825                     (forwardMostMove / 2) + 1,
9826                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9827             DisplayError(move, 0);
9828             done = TRUE;
9829         } else {
9830             if (appData.debugMode)
9831               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9832                       yy_text, currentMoveString);
9833             fromX = currentMoveString[0] - AAA;
9834             fromY = currentMoveString[1] - ONE;
9835             toX = currentMoveString[2] - AAA;
9836             toY = currentMoveString[3] - ONE;
9837             promoChar = currentMoveString[4];
9838         }
9839         break;
9840
9841       case AmbiguousMove:
9842         if (appData.debugMode)
9843           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9844         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9845                 (forwardMostMove / 2) + 1,
9846                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9847         DisplayError(move, 0);
9848         done = TRUE;
9849         break;
9850
9851       default:
9852       case ImpossibleMove:
9853         if (appData.debugMode)
9854           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9855         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9856                 (forwardMostMove / 2) + 1,
9857                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9858         DisplayError(move, 0);
9859         done = TRUE;
9860         break;
9861     }
9862
9863     if (done) {
9864         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9865             DrawPosition(FALSE, boards[currentMove]);
9866             DisplayBothClocks();
9867             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9868               DisplayComment(currentMove - 1, commentList[currentMove]);
9869         }
9870         (void) StopLoadGameTimer();
9871         gameFileFP = NULL;
9872         cmailOldMove = forwardMostMove;
9873         return FALSE;
9874     } else {
9875         /* currentMoveString is set as a side-effect of yylex */
9876         strcat(currentMoveString, "\n");
9877         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9878
9879         thinkOutput[0] = NULLCHAR;
9880         MakeMove(fromX, fromY, toX, toY, promoChar);
9881         currentMove = forwardMostMove;
9882         return TRUE;
9883     }
9884 }
9885
9886 /* Load the nth game from the given file */
9887 int
9888 LoadGameFromFile(filename, n, title, useList)
9889      char *filename;
9890      int n;
9891      char *title;
9892      /*Boolean*/ int useList;
9893 {
9894     FILE *f;
9895     char buf[MSG_SIZ];
9896
9897     if (strcmp(filename, "-") == 0) {
9898         f = stdin;
9899         title = "stdin";
9900     } else {
9901         f = fopen(filename, "rb");
9902         if (f == NULL) {
9903           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9904             DisplayError(buf, errno);
9905             return FALSE;
9906         }
9907     }
9908     if (fseek(f, 0, 0) == -1) {
9909         /* f is not seekable; probably a pipe */
9910         useList = FALSE;
9911     }
9912     if (useList && n == 0) {
9913         int error = GameListBuild(f);
9914         if (error) {
9915             DisplayError(_("Cannot build game list"), error);
9916         } else if (!ListEmpty(&gameList) &&
9917                    ((ListGame *) gameList.tailPred)->number > 1) {
9918             GameListPopUp(f, title);
9919             return TRUE;
9920         }
9921         GameListDestroy();
9922         n = 1;
9923     }
9924     if (n == 0) n = 1;
9925     return LoadGame(f, n, title, FALSE);
9926 }
9927
9928
9929 void
9930 MakeRegisteredMove()
9931 {
9932     int fromX, fromY, toX, toY;
9933     char promoChar;
9934     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9935         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9936           case CMAIL_MOVE:
9937           case CMAIL_DRAW:
9938             if (appData.debugMode)
9939               fprintf(debugFP, "Restoring %s for game %d\n",
9940                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9941
9942             thinkOutput[0] = NULLCHAR;
9943             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9944             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9945             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9946             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9947             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9948             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9949             MakeMove(fromX, fromY, toX, toY, promoChar);
9950             ShowMove(fromX, fromY, toX, toY);
9951
9952             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9953               case MT_NONE:
9954               case MT_CHECK:
9955                 break;
9956
9957               case MT_CHECKMATE:
9958               case MT_STAINMATE:
9959                 if (WhiteOnMove(currentMove)) {
9960                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9961                 } else {
9962                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9963                 }
9964                 break;
9965
9966               case MT_STALEMATE:
9967                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9968                 break;
9969             }
9970
9971             break;
9972
9973           case CMAIL_RESIGN:
9974             if (WhiteOnMove(currentMove)) {
9975                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9976             } else {
9977                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9978             }
9979             break;
9980
9981           case CMAIL_ACCEPT:
9982             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9983             break;
9984
9985           default:
9986             break;
9987         }
9988     }
9989
9990     return;
9991 }
9992
9993 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9994 int
9995 CmailLoadGame(f, gameNumber, title, useList)
9996      FILE *f;
9997      int gameNumber;
9998      char *title;
9999      int useList;
10000 {
10001     int retVal;
10002
10003     if (gameNumber > nCmailGames) {
10004         DisplayError(_("No more games in this message"), 0);
10005         return FALSE;
10006     }
10007     if (f == lastLoadGameFP) {
10008         int offset = gameNumber - lastLoadGameNumber;
10009         if (offset == 0) {
10010             cmailMsg[0] = NULLCHAR;
10011             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10012                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10013                 nCmailMovesRegistered--;
10014             }
10015             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10016             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10017                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10018             }
10019         } else {
10020             if (! RegisterMove()) return FALSE;
10021         }
10022     }
10023
10024     retVal = LoadGame(f, gameNumber, title, useList);
10025
10026     /* Make move registered during previous look at this game, if any */
10027     MakeRegisteredMove();
10028
10029     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10030         commentList[currentMove]
10031           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10032         DisplayComment(currentMove - 1, commentList[currentMove]);
10033     }
10034
10035     return retVal;
10036 }
10037
10038 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10039 int
10040 ReloadGame(offset)
10041      int offset;
10042 {
10043     int gameNumber = lastLoadGameNumber + offset;
10044     if (lastLoadGameFP == NULL) {
10045         DisplayError(_("No game has been loaded yet"), 0);
10046         return FALSE;
10047     }
10048     if (gameNumber <= 0) {
10049         DisplayError(_("Can't back up any further"), 0);
10050         return FALSE;
10051     }
10052     if (cmailMsgLoaded) {
10053         return CmailLoadGame(lastLoadGameFP, gameNumber,
10054                              lastLoadGameTitle, lastLoadGameUseList);
10055     } else {
10056         return LoadGame(lastLoadGameFP, gameNumber,
10057                         lastLoadGameTitle, lastLoadGameUseList);
10058     }
10059 }
10060
10061
10062
10063 /* Load the nth game from open file f */
10064 int
10065 LoadGame(f, gameNumber, title, useList)
10066      FILE *f;
10067      int gameNumber;
10068      char *title;
10069      int useList;
10070 {
10071     ChessMove cm;
10072     char buf[MSG_SIZ];
10073     int gn = gameNumber;
10074     ListGame *lg = NULL;
10075     int numPGNTags = 0;
10076     int err;
10077     GameMode oldGameMode;
10078     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10079
10080     if (appData.debugMode)
10081         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10082
10083     if (gameMode == Training )
10084         SetTrainingModeOff();
10085
10086     oldGameMode = gameMode;
10087     if (gameMode != BeginningOfGame) {
10088       Reset(FALSE, TRUE);
10089     }
10090
10091     gameFileFP = f;
10092     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10093         fclose(lastLoadGameFP);
10094     }
10095
10096     if (useList) {
10097         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10098
10099         if (lg) {
10100             fseek(f, lg->offset, 0);
10101             GameListHighlight(gameNumber);
10102             gn = 1;
10103         }
10104         else {
10105             DisplayError(_("Game number out of range"), 0);
10106             return FALSE;
10107         }
10108     } else {
10109         GameListDestroy();
10110         if (fseek(f, 0, 0) == -1) {
10111             if (f == lastLoadGameFP ?
10112                 gameNumber == lastLoadGameNumber + 1 :
10113                 gameNumber == 1) {
10114                 gn = 1;
10115             } else {
10116                 DisplayError(_("Can't seek on game file"), 0);
10117                 return FALSE;
10118             }
10119         }
10120     }
10121     lastLoadGameFP = f;
10122     lastLoadGameNumber = gameNumber;
10123     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10124     lastLoadGameUseList = useList;
10125
10126     yynewfile(f);
10127
10128     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10129       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10130                 lg->gameInfo.black);
10131             DisplayTitle(buf);
10132     } else if (*title != NULLCHAR) {
10133         if (gameNumber > 1) {
10134           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10135             DisplayTitle(buf);
10136         } else {
10137             DisplayTitle(title);
10138         }
10139     }
10140
10141     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10142         gameMode = PlayFromGameFile;
10143         ModeHighlight();
10144     }
10145
10146     currentMove = forwardMostMove = backwardMostMove = 0;
10147     CopyBoard(boards[0], initialPosition);
10148     StopClocks();
10149
10150     /*
10151      * Skip the first gn-1 games in the file.
10152      * Also skip over anything that precedes an identifiable
10153      * start of game marker, to avoid being confused by
10154      * garbage at the start of the file.  Currently
10155      * recognized start of game markers are the move number "1",
10156      * the pattern "gnuchess .* game", the pattern
10157      * "^[#;%] [^ ]* game file", and a PGN tag block.
10158      * A game that starts with one of the latter two patterns
10159      * will also have a move number 1, possibly
10160      * following a position diagram.
10161      * 5-4-02: Let's try being more lenient and allowing a game to
10162      * start with an unnumbered move.  Does that break anything?
10163      */
10164     cm = lastLoadGameStart = EndOfFile;
10165     while (gn > 0) {
10166         yyboardindex = forwardMostMove;
10167         cm = (ChessMove) Myylex();
10168         switch (cm) {
10169           case EndOfFile:
10170             if (cmailMsgLoaded) {
10171                 nCmailGames = CMAIL_MAX_GAMES - gn;
10172             } else {
10173                 Reset(TRUE, TRUE);
10174                 DisplayError(_("Game not found in file"), 0);
10175             }
10176             return FALSE;
10177
10178           case GNUChessGame:
10179           case XBoardGame:
10180             gn--;
10181             lastLoadGameStart = cm;
10182             break;
10183
10184           case MoveNumberOne:
10185             switch (lastLoadGameStart) {
10186               case GNUChessGame:
10187               case XBoardGame:
10188               case PGNTag:
10189                 break;
10190               case MoveNumberOne:
10191               case EndOfFile:
10192                 gn--;           /* count this game */
10193                 lastLoadGameStart = cm;
10194                 break;
10195               default:
10196                 /* impossible */
10197                 break;
10198             }
10199             break;
10200
10201           case PGNTag:
10202             switch (lastLoadGameStart) {
10203               case GNUChessGame:
10204               case PGNTag:
10205               case MoveNumberOne:
10206               case EndOfFile:
10207                 gn--;           /* count this game */
10208                 lastLoadGameStart = cm;
10209                 break;
10210               case XBoardGame:
10211                 lastLoadGameStart = cm; /* game counted already */
10212                 break;
10213               default:
10214                 /* impossible */
10215                 break;
10216             }
10217             if (gn > 0) {
10218                 do {
10219                     yyboardindex = forwardMostMove;
10220                     cm = (ChessMove) Myylex();
10221                 } while (cm == PGNTag || cm == Comment);
10222             }
10223             break;
10224
10225           case WhiteWins:
10226           case BlackWins:
10227           case GameIsDrawn:
10228             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10229                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10230                     != CMAIL_OLD_RESULT) {
10231                     nCmailResults ++ ;
10232                     cmailResult[  CMAIL_MAX_GAMES
10233                                 - gn - 1] = CMAIL_OLD_RESULT;
10234                 }
10235             }
10236             break;
10237
10238           case NormalMove:
10239             /* Only a NormalMove can be at the start of a game
10240              * without a position diagram. */
10241             if (lastLoadGameStart == EndOfFile ) {
10242               gn--;
10243               lastLoadGameStart = MoveNumberOne;
10244             }
10245             break;
10246
10247           default:
10248             break;
10249         }
10250     }
10251
10252     if (appData.debugMode)
10253       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10254
10255     if (cm == XBoardGame) {
10256         /* Skip any header junk before position diagram and/or move 1 */
10257         for (;;) {
10258             yyboardindex = forwardMostMove;
10259             cm = (ChessMove) Myylex();
10260
10261             if (cm == EndOfFile ||
10262                 cm == GNUChessGame || cm == XBoardGame) {
10263                 /* Empty game; pretend end-of-file and handle later */
10264                 cm = EndOfFile;
10265                 break;
10266             }
10267
10268             if (cm == MoveNumberOne || cm == PositionDiagram ||
10269                 cm == PGNTag || cm == Comment)
10270               break;
10271         }
10272     } else if (cm == GNUChessGame) {
10273         if (gameInfo.event != NULL) {
10274             free(gameInfo.event);
10275         }
10276         gameInfo.event = StrSave(yy_text);
10277     }
10278
10279     startedFromSetupPosition = FALSE;
10280     while (cm == PGNTag) {
10281         if (appData.debugMode)
10282           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10283         err = ParsePGNTag(yy_text, &gameInfo);
10284         if (!err) numPGNTags++;
10285
10286         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10287         if(gameInfo.variant != oldVariant) {
10288             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10289             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10290             InitPosition(TRUE);
10291             oldVariant = gameInfo.variant;
10292             if (appData.debugMode)
10293               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10294         }
10295
10296
10297         if (gameInfo.fen != NULL) {
10298           Board initial_position;
10299           startedFromSetupPosition = TRUE;
10300           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10301             Reset(TRUE, TRUE);
10302             DisplayError(_("Bad FEN position in file"), 0);
10303             return FALSE;
10304           }
10305           CopyBoard(boards[0], initial_position);
10306           if (blackPlaysFirst) {
10307             currentMove = forwardMostMove = backwardMostMove = 1;
10308             CopyBoard(boards[1], initial_position);
10309             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10310             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10311             timeRemaining[0][1] = whiteTimeRemaining;
10312             timeRemaining[1][1] = blackTimeRemaining;
10313             if (commentList[0] != NULL) {
10314               commentList[1] = commentList[0];
10315               commentList[0] = NULL;
10316             }
10317           } else {
10318             currentMove = forwardMostMove = backwardMostMove = 0;
10319           }
10320           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10321           {   int i;
10322               initialRulePlies = FENrulePlies;
10323               for( i=0; i< nrCastlingRights; i++ )
10324                   initialRights[i] = initial_position[CASTLING][i];
10325           }
10326           yyboardindex = forwardMostMove;
10327           free(gameInfo.fen);
10328           gameInfo.fen = NULL;
10329         }
10330
10331         yyboardindex = forwardMostMove;
10332         cm = (ChessMove) Myylex();
10333
10334         /* Handle comments interspersed among the tags */
10335         while (cm == Comment) {
10336             char *p;
10337             if (appData.debugMode)
10338               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10339             p = yy_text;
10340             AppendComment(currentMove, p, FALSE);
10341             yyboardindex = forwardMostMove;
10342             cm = (ChessMove) Myylex();
10343         }
10344     }
10345
10346     /* don't rely on existence of Event tag since if game was
10347      * pasted from clipboard the Event tag may not exist
10348      */
10349     if (numPGNTags > 0){
10350         char *tags;
10351         if (gameInfo.variant == VariantNormal) {
10352           VariantClass v = StringToVariant(gameInfo.event);
10353           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10354           if(v < VariantShogi) gameInfo.variant = v;
10355         }
10356         if (!matchMode) {
10357           if( appData.autoDisplayTags ) {
10358             tags = PGNTags(&gameInfo);
10359             TagsPopUp(tags, CmailMsg());
10360             free(tags);
10361           }
10362         }
10363     } else {
10364         /* Make something up, but don't display it now */
10365         SetGameInfo();
10366         TagsPopDown();
10367     }
10368
10369     if (cm == PositionDiagram) {
10370         int i, j;
10371         char *p;
10372         Board initial_position;
10373
10374         if (appData.debugMode)
10375           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10376
10377         if (!startedFromSetupPosition) {
10378             p = yy_text;
10379             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10380               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10381                 switch (*p) {
10382                   case '[':
10383                   case '-':
10384                   case ' ':
10385                   case '\t':
10386                   case '\n':
10387                   case '\r':
10388                     break;
10389                   default:
10390                     initial_position[i][j++] = CharToPiece(*p);
10391                     break;
10392                 }
10393             while (*p == ' ' || *p == '\t' ||
10394                    *p == '\n' || *p == '\r') p++;
10395
10396             if (strncmp(p, "black", strlen("black"))==0)
10397               blackPlaysFirst = TRUE;
10398             else
10399               blackPlaysFirst = FALSE;
10400             startedFromSetupPosition = TRUE;
10401
10402             CopyBoard(boards[0], initial_position);
10403             if (blackPlaysFirst) {
10404                 currentMove = forwardMostMove = backwardMostMove = 1;
10405                 CopyBoard(boards[1], initial_position);
10406                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10407                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10408                 timeRemaining[0][1] = whiteTimeRemaining;
10409                 timeRemaining[1][1] = blackTimeRemaining;
10410                 if (commentList[0] != NULL) {
10411                     commentList[1] = commentList[0];
10412                     commentList[0] = NULL;
10413                 }
10414             } else {
10415                 currentMove = forwardMostMove = backwardMostMove = 0;
10416             }
10417         }
10418         yyboardindex = forwardMostMove;
10419         cm = (ChessMove) Myylex();
10420     }
10421
10422     if (first.pr == NoProc) {
10423         StartChessProgram(&first);
10424     }
10425     InitChessProgram(&first, FALSE);
10426     SendToProgram("force\n", &first);
10427     if (startedFromSetupPosition) {
10428         SendBoard(&first, forwardMostMove);
10429     if (appData.debugMode) {
10430         fprintf(debugFP, "Load Game\n");
10431     }
10432         DisplayBothClocks();
10433     }
10434
10435     /* [HGM] server: flag to write setup moves in broadcast file as one */
10436     loadFlag = appData.suppressLoadMoves;
10437
10438     while (cm == Comment) {
10439         char *p;
10440         if (appData.debugMode)
10441           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10442         p = yy_text;
10443         AppendComment(currentMove, p, FALSE);
10444         yyboardindex = forwardMostMove;
10445         cm = (ChessMove) Myylex();
10446     }
10447
10448     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10449         cm == WhiteWins || cm == BlackWins ||
10450         cm == GameIsDrawn || cm == GameUnfinished) {
10451         DisplayMessage("", _("No moves in game"));
10452         if (cmailMsgLoaded) {
10453             if (appData.debugMode)
10454               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10455             ClearHighlights();
10456             flipView = FALSE;
10457         }
10458         DrawPosition(FALSE, boards[currentMove]);
10459         DisplayBothClocks();
10460         gameMode = EditGame;
10461         ModeHighlight();
10462         gameFileFP = NULL;
10463         cmailOldMove = 0;
10464         return TRUE;
10465     }
10466
10467     // [HGM] PV info: routine tests if comment empty
10468     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10469         DisplayComment(currentMove - 1, commentList[currentMove]);
10470     }
10471     if (!matchMode && appData.timeDelay != 0)
10472       DrawPosition(FALSE, boards[currentMove]);
10473
10474     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10475       programStats.ok_to_send = 1;
10476     }
10477
10478     /* if the first token after the PGN tags is a move
10479      * and not move number 1, retrieve it from the parser
10480      */
10481     if (cm != MoveNumberOne)
10482         LoadGameOneMove(cm);
10483
10484     /* load the remaining moves from the file */
10485     while (LoadGameOneMove(EndOfFile)) {
10486       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10487       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10488     }
10489
10490     /* rewind to the start of the game */
10491     currentMove = backwardMostMove;
10492
10493     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10494
10495     if (oldGameMode == AnalyzeFile ||
10496         oldGameMode == AnalyzeMode) {
10497       AnalyzeFileEvent();
10498     }
10499
10500     if (matchMode || appData.timeDelay == 0) {
10501       ToEndEvent();
10502       gameMode = EditGame;
10503       ModeHighlight();
10504     } else if (appData.timeDelay > 0) {
10505       AutoPlayGameLoop();
10506     }
10507
10508     if (appData.debugMode)
10509         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10510
10511     loadFlag = 0; /* [HGM] true game starts */
10512     return TRUE;
10513 }
10514
10515 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10516 int
10517 ReloadPosition(offset)
10518      int offset;
10519 {
10520     int positionNumber = lastLoadPositionNumber + offset;
10521     if (lastLoadPositionFP == NULL) {
10522         DisplayError(_("No position has been loaded yet"), 0);
10523         return FALSE;
10524     }
10525     if (positionNumber <= 0) {
10526         DisplayError(_("Can't back up any further"), 0);
10527         return FALSE;
10528     }
10529     return LoadPosition(lastLoadPositionFP, positionNumber,
10530                         lastLoadPositionTitle);
10531 }
10532
10533 /* Load the nth position from the given file */
10534 int
10535 LoadPositionFromFile(filename, n, title)
10536      char *filename;
10537      int n;
10538      char *title;
10539 {
10540     FILE *f;
10541     char buf[MSG_SIZ];
10542
10543     if (strcmp(filename, "-") == 0) {
10544         return LoadPosition(stdin, n, "stdin");
10545     } else {
10546         f = fopen(filename, "rb");
10547         if (f == NULL) {
10548             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10549             DisplayError(buf, errno);
10550             return FALSE;
10551         } else {
10552             return LoadPosition(f, n, title);
10553         }
10554     }
10555 }
10556
10557 /* Load the nth position from the given open file, and close it */
10558 int
10559 LoadPosition(f, positionNumber, title)
10560      FILE *f;
10561      int positionNumber;
10562      char *title;
10563 {
10564     char *p, line[MSG_SIZ];
10565     Board initial_position;
10566     int i, j, fenMode, pn;
10567
10568     if (gameMode == Training )
10569         SetTrainingModeOff();
10570
10571     if (gameMode != BeginningOfGame) {
10572         Reset(FALSE, TRUE);
10573     }
10574     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10575         fclose(lastLoadPositionFP);
10576     }
10577     if (positionNumber == 0) positionNumber = 1;
10578     lastLoadPositionFP = f;
10579     lastLoadPositionNumber = positionNumber;
10580     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10581     if (first.pr == NoProc) {
10582       StartChessProgram(&first);
10583       InitChessProgram(&first, FALSE);
10584     }
10585     pn = positionNumber;
10586     if (positionNumber < 0) {
10587         /* Negative position number means to seek to that byte offset */
10588         if (fseek(f, -positionNumber, 0) == -1) {
10589             DisplayError(_("Can't seek on position file"), 0);
10590             return FALSE;
10591         };
10592         pn = 1;
10593     } else {
10594         if (fseek(f, 0, 0) == -1) {
10595             if (f == lastLoadPositionFP ?
10596                 positionNumber == lastLoadPositionNumber + 1 :
10597                 positionNumber == 1) {
10598                 pn = 1;
10599             } else {
10600                 DisplayError(_("Can't seek on position file"), 0);
10601                 return FALSE;
10602             }
10603         }
10604     }
10605     /* See if this file is FEN or old-style xboard */
10606     if (fgets(line, MSG_SIZ, f) == NULL) {
10607         DisplayError(_("Position not found in file"), 0);
10608         return FALSE;
10609     }
10610     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10611     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10612
10613     if (pn >= 2) {
10614         if (fenMode || line[0] == '#') pn--;
10615         while (pn > 0) {
10616             /* skip positions before number pn */
10617             if (fgets(line, MSG_SIZ, f) == NULL) {
10618                 Reset(TRUE, TRUE);
10619                 DisplayError(_("Position not found in file"), 0);
10620                 return FALSE;
10621             }
10622             if (fenMode || line[0] == '#') pn--;
10623         }
10624     }
10625
10626     if (fenMode) {
10627         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10628             DisplayError(_("Bad FEN position in file"), 0);
10629             return FALSE;
10630         }
10631     } else {
10632         (void) fgets(line, MSG_SIZ, f);
10633         (void) fgets(line, MSG_SIZ, f);
10634
10635         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10636             (void) fgets(line, MSG_SIZ, f);
10637             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10638                 if (*p == ' ')
10639                   continue;
10640                 initial_position[i][j++] = CharToPiece(*p);
10641             }
10642         }
10643
10644         blackPlaysFirst = FALSE;
10645         if (!feof(f)) {
10646             (void) fgets(line, MSG_SIZ, f);
10647             if (strncmp(line, "black", strlen("black"))==0)
10648               blackPlaysFirst = TRUE;
10649         }
10650     }
10651     startedFromSetupPosition = TRUE;
10652
10653     SendToProgram("force\n", &first);
10654     CopyBoard(boards[0], initial_position);
10655     if (blackPlaysFirst) {
10656         currentMove = forwardMostMove = backwardMostMove = 1;
10657         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10658         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10659         CopyBoard(boards[1], initial_position);
10660         DisplayMessage("", _("Black to play"));
10661     } else {
10662         currentMove = forwardMostMove = backwardMostMove = 0;
10663         DisplayMessage("", _("White to play"));
10664     }
10665     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10666     SendBoard(&first, forwardMostMove);
10667     if (appData.debugMode) {
10668 int i, j;
10669   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10670   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10671         fprintf(debugFP, "Load Position\n");
10672     }
10673
10674     if (positionNumber > 1) {
10675       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10676         DisplayTitle(line);
10677     } else {
10678         DisplayTitle(title);
10679     }
10680     gameMode = EditGame;
10681     ModeHighlight();
10682     ResetClocks();
10683     timeRemaining[0][1] = whiteTimeRemaining;
10684     timeRemaining[1][1] = blackTimeRemaining;
10685     DrawPosition(FALSE, boards[currentMove]);
10686
10687     return TRUE;
10688 }
10689
10690
10691 void
10692 CopyPlayerNameIntoFileName(dest, src)
10693      char **dest, *src;
10694 {
10695     while (*src != NULLCHAR && *src != ',') {
10696         if (*src == ' ') {
10697             *(*dest)++ = '_';
10698             src++;
10699         } else {
10700             *(*dest)++ = *src++;
10701         }
10702     }
10703 }
10704
10705 char *DefaultFileName(ext)
10706      char *ext;
10707 {
10708     static char def[MSG_SIZ];
10709     char *p;
10710
10711     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10712         p = def;
10713         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10714         *p++ = '-';
10715         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10716         *p++ = '.';
10717         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10718     } else {
10719         def[0] = NULLCHAR;
10720     }
10721     return def;
10722 }
10723
10724 /* Save the current game to the given file */
10725 int
10726 SaveGameToFile(filename, append)
10727      char *filename;
10728      int append;
10729 {
10730     FILE *f;
10731     char buf[MSG_SIZ];
10732
10733     if (strcmp(filename, "-") == 0) {
10734         return SaveGame(stdout, 0, NULL);
10735     } else {
10736         f = fopen(filename, append ? "a" : "w");
10737         if (f == NULL) {
10738             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10739             DisplayError(buf, errno);
10740             return FALSE;
10741         } else {
10742             return SaveGame(f, 0, NULL);
10743         }
10744     }
10745 }
10746
10747 char *
10748 SavePart(str)
10749      char *str;
10750 {
10751     static char buf[MSG_SIZ];
10752     char *p;
10753
10754     p = strchr(str, ' ');
10755     if (p == NULL) return str;
10756     strncpy(buf, str, p - str);
10757     buf[p - str] = NULLCHAR;
10758     return buf;
10759 }
10760
10761 #define PGN_MAX_LINE 75
10762
10763 #define PGN_SIDE_WHITE  0
10764 #define PGN_SIDE_BLACK  1
10765
10766 /* [AS] */
10767 static int FindFirstMoveOutOfBook( int side )
10768 {
10769     int result = -1;
10770
10771     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10772         int index = backwardMostMove;
10773         int has_book_hit = 0;
10774
10775         if( (index % 2) != side ) {
10776             index++;
10777         }
10778
10779         while( index < forwardMostMove ) {
10780             /* Check to see if engine is in book */
10781             int depth = pvInfoList[index].depth;
10782             int score = pvInfoList[index].score;
10783             int in_book = 0;
10784
10785             if( depth <= 2 ) {
10786                 in_book = 1;
10787             }
10788             else if( score == 0 && depth == 63 ) {
10789                 in_book = 1; /* Zappa */
10790             }
10791             else if( score == 2 && depth == 99 ) {
10792                 in_book = 1; /* Abrok */
10793             }
10794
10795             has_book_hit += in_book;
10796
10797             if( ! in_book ) {
10798                 result = index;
10799
10800                 break;
10801             }
10802
10803             index += 2;
10804         }
10805     }
10806
10807     return result;
10808 }
10809
10810 /* [AS] */
10811 void GetOutOfBookInfo( char * buf )
10812 {
10813     int oob[2];
10814     int i;
10815     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10816
10817     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10818     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10819
10820     *buf = '\0';
10821
10822     if( oob[0] >= 0 || oob[1] >= 0 ) {
10823         for( i=0; i<2; i++ ) {
10824             int idx = oob[i];
10825
10826             if( idx >= 0 ) {
10827                 if( i > 0 && oob[0] >= 0 ) {
10828                     strcat( buf, "   " );
10829                 }
10830
10831                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10832                 sprintf( buf+strlen(buf), "%s%.2f",
10833                     pvInfoList[idx].score >= 0 ? "+" : "",
10834                     pvInfoList[idx].score / 100.0 );
10835             }
10836         }
10837     }
10838 }
10839
10840 /* Save game in PGN style and close the file */
10841 int
10842 SaveGamePGN(f)
10843      FILE *f;
10844 {
10845     int i, offset, linelen, newblock;
10846     time_t tm;
10847 //    char *movetext;
10848     char numtext[32];
10849     int movelen, numlen, blank;
10850     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10851
10852     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10853
10854     tm = time((time_t *) NULL);
10855
10856     PrintPGNTags(f, &gameInfo);
10857
10858     if (backwardMostMove > 0 || startedFromSetupPosition) {
10859         char *fen = PositionToFEN(backwardMostMove, NULL);
10860         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10861         fprintf(f, "\n{--------------\n");
10862         PrintPosition(f, backwardMostMove);
10863         fprintf(f, "--------------}\n");
10864         free(fen);
10865     }
10866     else {
10867         /* [AS] Out of book annotation */
10868         if( appData.saveOutOfBookInfo ) {
10869             char buf[64];
10870
10871             GetOutOfBookInfo( buf );
10872
10873             if( buf[0] != '\0' ) {
10874                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10875             }
10876         }
10877
10878         fprintf(f, "\n");
10879     }
10880
10881     i = backwardMostMove;
10882     linelen = 0;
10883     newblock = TRUE;
10884
10885     while (i < forwardMostMove) {
10886         /* Print comments preceding this move */
10887         if (commentList[i] != NULL) {
10888             if (linelen > 0) fprintf(f, "\n");
10889             fprintf(f, "%s", commentList[i]);
10890             linelen = 0;
10891             newblock = TRUE;
10892         }
10893
10894         /* Format move number */
10895         if ((i % 2) == 0)
10896           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10897         else
10898           if (newblock)
10899             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10900           else
10901             numtext[0] = NULLCHAR;
10902
10903         numlen = strlen(numtext);
10904         newblock = FALSE;
10905
10906         /* Print move number */
10907         blank = linelen > 0 && numlen > 0;
10908         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10909             fprintf(f, "\n");
10910             linelen = 0;
10911             blank = 0;
10912         }
10913         if (blank) {
10914             fprintf(f, " ");
10915             linelen++;
10916         }
10917         fprintf(f, "%s", numtext);
10918         linelen += numlen;
10919
10920         /* Get move */
10921         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10922         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10923
10924         /* Print move */
10925         blank = linelen > 0 && movelen > 0;
10926         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10927             fprintf(f, "\n");
10928             linelen = 0;
10929             blank = 0;
10930         }
10931         if (blank) {
10932             fprintf(f, " ");
10933             linelen++;
10934         }
10935         fprintf(f, "%s", move_buffer);
10936         linelen += movelen;
10937
10938         /* [AS] Add PV info if present */
10939         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10940             /* [HGM] add time */
10941             char buf[MSG_SIZ]; int seconds;
10942
10943             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10944
10945             if( seconds <= 0)
10946               buf[0] = 0;
10947             else
10948               if( seconds < 30 )
10949                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10950               else
10951                 {
10952                   seconds = (seconds + 4)/10; // round to full seconds
10953                   if( seconds < 60 )
10954                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10955                   else
10956                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10957                 }
10958
10959             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10960                       pvInfoList[i].score >= 0 ? "+" : "",
10961                       pvInfoList[i].score / 100.0,
10962                       pvInfoList[i].depth,
10963                       buf );
10964
10965             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10966
10967             /* Print score/depth */
10968             blank = linelen > 0 && movelen > 0;
10969             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10970                 fprintf(f, "\n");
10971                 linelen = 0;
10972                 blank = 0;
10973             }
10974             if (blank) {
10975                 fprintf(f, " ");
10976                 linelen++;
10977             }
10978             fprintf(f, "%s", move_buffer);
10979             linelen += movelen;
10980         }
10981
10982         i++;
10983     }
10984
10985     /* Start a new line */
10986     if (linelen > 0) fprintf(f, "\n");
10987
10988     /* Print comments after last move */
10989     if (commentList[i] != NULL) {
10990         fprintf(f, "%s\n", commentList[i]);
10991     }
10992
10993     /* Print result */
10994     if (gameInfo.resultDetails != NULL &&
10995         gameInfo.resultDetails[0] != NULLCHAR) {
10996         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10997                 PGNResult(gameInfo.result));
10998     } else {
10999         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11000     }
11001
11002     fclose(f);
11003     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11004     return TRUE;
11005 }
11006
11007 /* Save game in old style and close the file */
11008 int
11009 SaveGameOldStyle(f)
11010      FILE *f;
11011 {
11012     int i, offset;
11013     time_t tm;
11014
11015     tm = time((time_t *) NULL);
11016
11017     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11018     PrintOpponents(f);
11019
11020     if (backwardMostMove > 0 || startedFromSetupPosition) {
11021         fprintf(f, "\n[--------------\n");
11022         PrintPosition(f, backwardMostMove);
11023         fprintf(f, "--------------]\n");
11024     } else {
11025         fprintf(f, "\n");
11026     }
11027
11028     i = backwardMostMove;
11029     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11030
11031     while (i < forwardMostMove) {
11032         if (commentList[i] != NULL) {
11033             fprintf(f, "[%s]\n", commentList[i]);
11034         }
11035
11036         if ((i % 2) == 1) {
11037             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11038             i++;
11039         } else {
11040             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11041             i++;
11042             if (commentList[i] != NULL) {
11043                 fprintf(f, "\n");
11044                 continue;
11045             }
11046             if (i >= forwardMostMove) {
11047                 fprintf(f, "\n");
11048                 break;
11049             }
11050             fprintf(f, "%s\n", parseList[i]);
11051             i++;
11052         }
11053     }
11054
11055     if (commentList[i] != NULL) {
11056         fprintf(f, "[%s]\n", commentList[i]);
11057     }
11058
11059     /* This isn't really the old style, but it's close enough */
11060     if (gameInfo.resultDetails != NULL &&
11061         gameInfo.resultDetails[0] != NULLCHAR) {
11062         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11063                 gameInfo.resultDetails);
11064     } else {
11065         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11066     }
11067
11068     fclose(f);
11069     return TRUE;
11070 }
11071
11072 /* Save the current game to open file f and close the file */
11073 int
11074 SaveGame(f, dummy, dummy2)
11075      FILE *f;
11076      int dummy;
11077      char *dummy2;
11078 {
11079     if (gameMode == EditPosition) EditPositionDone(TRUE);
11080     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11081     if (appData.oldSaveStyle)
11082       return SaveGameOldStyle(f);
11083     else
11084       return SaveGamePGN(f);
11085 }
11086
11087 /* Save the current position to the given file */
11088 int
11089 SavePositionToFile(filename)
11090      char *filename;
11091 {
11092     FILE *f;
11093     char buf[MSG_SIZ];
11094
11095     if (strcmp(filename, "-") == 0) {
11096         return SavePosition(stdout, 0, NULL);
11097     } else {
11098         f = fopen(filename, "a");
11099         if (f == NULL) {
11100             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11101             DisplayError(buf, errno);
11102             return FALSE;
11103         } else {
11104             SavePosition(f, 0, NULL);
11105             return TRUE;
11106         }
11107     }
11108 }
11109
11110 /* Save the current position to the given open file and close the file */
11111 int
11112 SavePosition(f, dummy, dummy2)
11113      FILE *f;
11114      int dummy;
11115      char *dummy2;
11116 {
11117     time_t tm;
11118     char *fen;
11119
11120     if (gameMode == EditPosition) EditPositionDone(TRUE);
11121     if (appData.oldSaveStyle) {
11122         tm = time((time_t *) NULL);
11123
11124         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11125         PrintOpponents(f);
11126         fprintf(f, "[--------------\n");
11127         PrintPosition(f, currentMove);
11128         fprintf(f, "--------------]\n");
11129     } else {
11130         fen = PositionToFEN(currentMove, NULL);
11131         fprintf(f, "%s\n", fen);
11132         free(fen);
11133     }
11134     fclose(f);
11135     return TRUE;
11136 }
11137
11138 void
11139 ReloadCmailMsgEvent(unregister)
11140      int unregister;
11141 {
11142 #if !WIN32
11143     static char *inFilename = NULL;
11144     static char *outFilename;
11145     int i;
11146     struct stat inbuf, outbuf;
11147     int status;
11148
11149     /* Any registered moves are unregistered if unregister is set, */
11150     /* i.e. invoked by the signal handler */
11151     if (unregister) {
11152         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11153             cmailMoveRegistered[i] = FALSE;
11154             if (cmailCommentList[i] != NULL) {
11155                 free(cmailCommentList[i]);
11156                 cmailCommentList[i] = NULL;
11157             }
11158         }
11159         nCmailMovesRegistered = 0;
11160     }
11161
11162     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11163         cmailResult[i] = CMAIL_NOT_RESULT;
11164     }
11165     nCmailResults = 0;
11166
11167     if (inFilename == NULL) {
11168         /* Because the filenames are static they only get malloced once  */
11169         /* and they never get freed                                      */
11170         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11171         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11172
11173         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11174         sprintf(outFilename, "%s.out", appData.cmailGameName);
11175     }
11176
11177     status = stat(outFilename, &outbuf);
11178     if (status < 0) {
11179         cmailMailedMove = FALSE;
11180     } else {
11181         status = stat(inFilename, &inbuf);
11182         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11183     }
11184
11185     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11186        counts the games, notes how each one terminated, etc.
11187
11188        It would be nice to remove this kludge and instead gather all
11189        the information while building the game list.  (And to keep it
11190        in the game list nodes instead of having a bunch of fixed-size
11191        parallel arrays.)  Note this will require getting each game's
11192        termination from the PGN tags, as the game list builder does
11193        not process the game moves.  --mann
11194        */
11195     cmailMsgLoaded = TRUE;
11196     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11197
11198     /* Load first game in the file or popup game menu */
11199     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11200
11201 #endif /* !WIN32 */
11202     return;
11203 }
11204
11205 int
11206 RegisterMove()
11207 {
11208     FILE *f;
11209     char string[MSG_SIZ];
11210
11211     if (   cmailMailedMove
11212         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11213         return TRUE;            /* Allow free viewing  */
11214     }
11215
11216     /* Unregister move to ensure that we don't leave RegisterMove        */
11217     /* with the move registered when the conditions for registering no   */
11218     /* longer hold                                                       */
11219     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11220         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11221         nCmailMovesRegistered --;
11222
11223         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11224           {
11225               free(cmailCommentList[lastLoadGameNumber - 1]);
11226               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11227           }
11228     }
11229
11230     if (cmailOldMove == -1) {
11231         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11232         return FALSE;
11233     }
11234
11235     if (currentMove > cmailOldMove + 1) {
11236         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11237         return FALSE;
11238     }
11239
11240     if (currentMove < cmailOldMove) {
11241         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11242         return FALSE;
11243     }
11244
11245     if (forwardMostMove > currentMove) {
11246         /* Silently truncate extra moves */
11247         TruncateGame();
11248     }
11249
11250     if (   (currentMove == cmailOldMove + 1)
11251         || (   (currentMove == cmailOldMove)
11252             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11253                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11254         if (gameInfo.result != GameUnfinished) {
11255             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11256         }
11257
11258         if (commentList[currentMove] != NULL) {
11259             cmailCommentList[lastLoadGameNumber - 1]
11260               = StrSave(commentList[currentMove]);
11261         }
11262         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11263
11264         if (appData.debugMode)
11265           fprintf(debugFP, "Saving %s for game %d\n",
11266                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11267
11268         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11269
11270         f = fopen(string, "w");
11271         if (appData.oldSaveStyle) {
11272             SaveGameOldStyle(f); /* also closes the file */
11273
11274             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11275             f = fopen(string, "w");
11276             SavePosition(f, 0, NULL); /* also closes the file */
11277         } else {
11278             fprintf(f, "{--------------\n");
11279             PrintPosition(f, currentMove);
11280             fprintf(f, "--------------}\n\n");
11281
11282             SaveGame(f, 0, NULL); /* also closes the file*/
11283         }
11284
11285         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11286         nCmailMovesRegistered ++;
11287     } else if (nCmailGames == 1) {
11288         DisplayError(_("You have not made a move yet"), 0);
11289         return FALSE;
11290     }
11291
11292     return TRUE;
11293 }
11294
11295 void
11296 MailMoveEvent()
11297 {
11298 #if !WIN32
11299     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11300     FILE *commandOutput;
11301     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11302     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11303     int nBuffers;
11304     int i;
11305     int archived;
11306     char *arcDir;
11307
11308     if (! cmailMsgLoaded) {
11309         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11310         return;
11311     }
11312
11313     if (nCmailGames == nCmailResults) {
11314         DisplayError(_("No unfinished games"), 0);
11315         return;
11316     }
11317
11318 #if CMAIL_PROHIBIT_REMAIL
11319     if (cmailMailedMove) {
11320       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);
11321         DisplayError(msg, 0);
11322         return;
11323     }
11324 #endif
11325
11326     if (! (cmailMailedMove || RegisterMove())) return;
11327
11328     if (   cmailMailedMove
11329         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11330       snprintf(string, MSG_SIZ, partCommandString,
11331                appData.debugMode ? " -v" : "", appData.cmailGameName);
11332         commandOutput = popen(string, "r");
11333
11334         if (commandOutput == NULL) {
11335             DisplayError(_("Failed to invoke cmail"), 0);
11336         } else {
11337             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11338                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11339             }
11340             if (nBuffers > 1) {
11341                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11342                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11343                 nBytes = MSG_SIZ - 1;
11344             } else {
11345                 (void) memcpy(msg, buffer, nBytes);
11346             }
11347             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11348
11349             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11350                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11351
11352                 archived = TRUE;
11353                 for (i = 0; i < nCmailGames; i ++) {
11354                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11355                         archived = FALSE;
11356                     }
11357                 }
11358                 if (   archived
11359                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11360                         != NULL)) {
11361                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11362                            arcDir,
11363                            appData.cmailGameName,
11364                            gameInfo.date);
11365                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11366                     cmailMsgLoaded = FALSE;
11367                 }
11368             }
11369
11370             DisplayInformation(msg);
11371             pclose(commandOutput);
11372         }
11373     } else {
11374         if ((*cmailMsg) != '\0') {
11375             DisplayInformation(cmailMsg);
11376         }
11377     }
11378
11379     return;
11380 #endif /* !WIN32 */
11381 }
11382
11383 char *
11384 CmailMsg()
11385 {
11386 #if WIN32
11387     return NULL;
11388 #else
11389     int  prependComma = 0;
11390     char number[5];
11391     char string[MSG_SIZ];       /* Space for game-list */
11392     int  i;
11393
11394     if (!cmailMsgLoaded) return "";
11395
11396     if (cmailMailedMove) {
11397       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11398     } else {
11399         /* Create a list of games left */
11400       snprintf(string, MSG_SIZ, "[");
11401         for (i = 0; i < nCmailGames; i ++) {
11402             if (! (   cmailMoveRegistered[i]
11403                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11404                 if (prependComma) {
11405                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11406                 } else {
11407                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11408                     prependComma = 1;
11409                 }
11410
11411                 strcat(string, number);
11412             }
11413         }
11414         strcat(string, "]");
11415
11416         if (nCmailMovesRegistered + nCmailResults == 0) {
11417             switch (nCmailGames) {
11418               case 1:
11419                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11420                 break;
11421
11422               case 2:
11423                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11424                 break;
11425
11426               default:
11427                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11428                          nCmailGames);
11429                 break;
11430             }
11431         } else {
11432             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11433               case 1:
11434                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11435                          string);
11436                 break;
11437
11438               case 0:
11439                 if (nCmailResults == nCmailGames) {
11440                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11441                 } else {
11442                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11443                 }
11444                 break;
11445
11446               default:
11447                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11448                          string);
11449             }
11450         }
11451     }
11452     return cmailMsg;
11453 #endif /* WIN32 */
11454 }
11455
11456 void
11457 ResetGameEvent()
11458 {
11459     if (gameMode == Training)
11460       SetTrainingModeOff();
11461
11462     Reset(TRUE, TRUE);
11463     cmailMsgLoaded = FALSE;
11464     if (appData.icsActive) {
11465       SendToICS(ics_prefix);
11466       SendToICS("refresh\n");
11467     }
11468 }
11469
11470 void
11471 ExitEvent(status)
11472      int status;
11473 {
11474     exiting++;
11475     if (exiting > 2) {
11476       /* Give up on clean exit */
11477       exit(status);
11478     }
11479     if (exiting > 1) {
11480       /* Keep trying for clean exit */
11481       return;
11482     }
11483
11484     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11485
11486     if (telnetISR != NULL) {
11487       RemoveInputSource(telnetISR);
11488     }
11489     if (icsPR != NoProc) {
11490       DestroyChildProcess(icsPR, TRUE);
11491     }
11492
11493     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11494     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11495
11496     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11497     /* make sure this other one finishes before killing it!                  */
11498     if(endingGame) { int count = 0;
11499         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11500         while(endingGame && count++ < 10) DoSleep(1);
11501         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11502     }
11503
11504     /* Kill off chess programs */
11505     if (first.pr != NoProc) {
11506         ExitAnalyzeMode();
11507
11508         DoSleep( appData.delayBeforeQuit );
11509         SendToProgram("quit\n", &first);
11510         DoSleep( appData.delayAfterQuit );
11511         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11512     }
11513     if (second.pr != NoProc) {
11514         DoSleep( appData.delayBeforeQuit );
11515         SendToProgram("quit\n", &second);
11516         DoSleep( appData.delayAfterQuit );
11517         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11518     }
11519     if (first.isr != NULL) {
11520         RemoveInputSource(first.isr);
11521     }
11522     if (second.isr != NULL) {
11523         RemoveInputSource(second.isr);
11524     }
11525
11526     ShutDownFrontEnd();
11527     exit(status);
11528 }
11529
11530 void
11531 PauseEvent()
11532 {
11533     if (appData.debugMode)
11534         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11535     if (pausing) {
11536         pausing = FALSE;
11537         ModeHighlight();
11538         if (gameMode == MachinePlaysWhite ||
11539             gameMode == MachinePlaysBlack) {
11540             StartClocks();
11541         } else {
11542             DisplayBothClocks();
11543         }
11544         if (gameMode == PlayFromGameFile) {
11545             if (appData.timeDelay >= 0)
11546                 AutoPlayGameLoop();
11547         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11548             Reset(FALSE, TRUE);
11549             SendToICS(ics_prefix);
11550             SendToICS("refresh\n");
11551         } else if (currentMove < forwardMostMove) {
11552             ForwardInner(forwardMostMove);
11553         }
11554         pauseExamInvalid = FALSE;
11555     } else {
11556         switch (gameMode) {
11557           default:
11558             return;
11559           case IcsExamining:
11560             pauseExamForwardMostMove = forwardMostMove;
11561             pauseExamInvalid = FALSE;
11562             /* fall through */
11563           case IcsObserving:
11564           case IcsPlayingWhite:
11565           case IcsPlayingBlack:
11566             pausing = TRUE;
11567             ModeHighlight();
11568             return;
11569           case PlayFromGameFile:
11570             (void) StopLoadGameTimer();
11571             pausing = TRUE;
11572             ModeHighlight();
11573             break;
11574           case BeginningOfGame:
11575             if (appData.icsActive) return;
11576             /* else fall through */
11577           case MachinePlaysWhite:
11578           case MachinePlaysBlack:
11579           case TwoMachinesPlay:
11580             if (forwardMostMove == 0)
11581               return;           /* don't pause if no one has moved */
11582             if ((gameMode == MachinePlaysWhite &&
11583                  !WhiteOnMove(forwardMostMove)) ||
11584                 (gameMode == MachinePlaysBlack &&
11585                  WhiteOnMove(forwardMostMove))) {
11586                 StopClocks();
11587             }
11588             pausing = TRUE;
11589             ModeHighlight();
11590             break;
11591         }
11592     }
11593 }
11594
11595 void
11596 EditCommentEvent()
11597 {
11598     char title[MSG_SIZ];
11599
11600     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11601       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11602     } else {
11603       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11604                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11605                parseList[currentMove - 1]);
11606     }
11607
11608     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11609 }
11610
11611
11612 void
11613 EditTagsEvent()
11614 {
11615     char *tags = PGNTags(&gameInfo);
11616     EditTagsPopUp(tags);
11617     free(tags);
11618 }
11619
11620 void
11621 AnalyzeModeEvent()
11622 {
11623     if (appData.noChessProgram || gameMode == AnalyzeMode)
11624       return;
11625
11626     if (gameMode != AnalyzeFile) {
11627         if (!appData.icsEngineAnalyze) {
11628                EditGameEvent();
11629                if (gameMode != EditGame) return;
11630         }
11631         ResurrectChessProgram();
11632         SendToProgram("analyze\n", &first);
11633         first.analyzing = TRUE;
11634         /*first.maybeThinking = TRUE;*/
11635         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11636         EngineOutputPopUp();
11637     }
11638     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11639     pausing = FALSE;
11640     ModeHighlight();
11641     SetGameInfo();
11642
11643     StartAnalysisClock();
11644     GetTimeMark(&lastNodeCountTime);
11645     lastNodeCount = 0;
11646 }
11647
11648 void
11649 AnalyzeFileEvent()
11650 {
11651     if (appData.noChessProgram || gameMode == AnalyzeFile)
11652       return;
11653
11654     if (gameMode != AnalyzeMode) {
11655         EditGameEvent();
11656         if (gameMode != EditGame) return;
11657         ResurrectChessProgram();
11658         SendToProgram("analyze\n", &first);
11659         first.analyzing = TRUE;
11660         /*first.maybeThinking = TRUE;*/
11661         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11662         EngineOutputPopUp();
11663     }
11664     gameMode = AnalyzeFile;
11665     pausing = FALSE;
11666     ModeHighlight();
11667     SetGameInfo();
11668
11669     StartAnalysisClock();
11670     GetTimeMark(&lastNodeCountTime);
11671     lastNodeCount = 0;
11672 }
11673
11674 void
11675 MachineWhiteEvent()
11676 {
11677     char buf[MSG_SIZ];
11678     char *bookHit = NULL;
11679
11680     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11681       return;
11682
11683
11684     if (gameMode == PlayFromGameFile ||
11685         gameMode == TwoMachinesPlay  ||
11686         gameMode == Training         ||
11687         gameMode == AnalyzeMode      ||
11688         gameMode == EndOfGame)
11689         EditGameEvent();
11690
11691     if (gameMode == EditPosition)
11692         EditPositionDone(TRUE);
11693
11694     if (!WhiteOnMove(currentMove)) {
11695         DisplayError(_("It is not White's turn"), 0);
11696         return;
11697     }
11698
11699     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11700       ExitAnalyzeMode();
11701
11702     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11703         gameMode == AnalyzeFile)
11704         TruncateGame();
11705
11706     ResurrectChessProgram();    /* in case it isn't running */
11707     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11708         gameMode = MachinePlaysWhite;
11709         ResetClocks();
11710     } else
11711     gameMode = MachinePlaysWhite;
11712     pausing = FALSE;
11713     ModeHighlight();
11714     SetGameInfo();
11715     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11716     DisplayTitle(buf);
11717     if (first.sendName) {
11718       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11719       SendToProgram(buf, &first);
11720     }
11721     if (first.sendTime) {
11722       if (first.useColors) {
11723         SendToProgram("black\n", &first); /*gnu kludge*/
11724       }
11725       SendTimeRemaining(&first, TRUE);
11726     }
11727     if (first.useColors) {
11728       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11729     }
11730     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11731     SetMachineThinkingEnables();
11732     first.maybeThinking = TRUE;
11733     StartClocks();
11734     firstMove = FALSE;
11735
11736     if (appData.autoFlipView && !flipView) {
11737       flipView = !flipView;
11738       DrawPosition(FALSE, NULL);
11739       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11740     }
11741
11742     if(bookHit) { // [HGM] book: simulate book reply
11743         static char bookMove[MSG_SIZ]; // a bit generous?
11744
11745         programStats.nodes = programStats.depth = programStats.time =
11746         programStats.score = programStats.got_only_move = 0;
11747         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11748
11749         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11750         strcat(bookMove, bookHit);
11751         HandleMachineMove(bookMove, &first);
11752     }
11753 }
11754
11755 void
11756 MachineBlackEvent()
11757 {
11758   char buf[MSG_SIZ];
11759   char *bookHit = NULL;
11760
11761     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11762         return;
11763
11764
11765     if (gameMode == PlayFromGameFile ||
11766         gameMode == TwoMachinesPlay  ||
11767         gameMode == Training         ||
11768         gameMode == AnalyzeMode      ||
11769         gameMode == EndOfGame)
11770         EditGameEvent();
11771
11772     if (gameMode == EditPosition)
11773         EditPositionDone(TRUE);
11774
11775     if (WhiteOnMove(currentMove)) {
11776         DisplayError(_("It is not Black's turn"), 0);
11777         return;
11778     }
11779
11780     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11781       ExitAnalyzeMode();
11782
11783     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11784         gameMode == AnalyzeFile)
11785         TruncateGame();
11786
11787     ResurrectChessProgram();    /* in case it isn't running */
11788     gameMode = MachinePlaysBlack;
11789     pausing = FALSE;
11790     ModeHighlight();
11791     SetGameInfo();
11792     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11793     DisplayTitle(buf);
11794     if (first.sendName) {
11795       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11796       SendToProgram(buf, &first);
11797     }
11798     if (first.sendTime) {
11799       if (first.useColors) {
11800         SendToProgram("white\n", &first); /*gnu kludge*/
11801       }
11802       SendTimeRemaining(&first, FALSE);
11803     }
11804     if (first.useColors) {
11805       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11806     }
11807     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11808     SetMachineThinkingEnables();
11809     first.maybeThinking = TRUE;
11810     StartClocks();
11811
11812     if (appData.autoFlipView && flipView) {
11813       flipView = !flipView;
11814       DrawPosition(FALSE, NULL);
11815       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11816     }
11817     if(bookHit) { // [HGM] book: simulate book reply
11818         static char bookMove[MSG_SIZ]; // a bit generous?
11819
11820         programStats.nodes = programStats.depth = programStats.time =
11821         programStats.score = programStats.got_only_move = 0;
11822         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11823
11824         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11825         strcat(bookMove, bookHit);
11826         HandleMachineMove(bookMove, &first);
11827     }
11828 }
11829
11830
11831 void
11832 DisplayTwoMachinesTitle()
11833 {
11834     char buf[MSG_SIZ];
11835     if (appData.matchGames > 0) {
11836         if (first.twoMachinesColor[0] == 'w') {
11837           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11838                    gameInfo.white, gameInfo.black,
11839                    first.matchWins, second.matchWins,
11840                    matchGame - 1 - (first.matchWins + second.matchWins));
11841         } else {
11842           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11843                    gameInfo.white, gameInfo.black,
11844                    second.matchWins, first.matchWins,
11845                    matchGame - 1 - (first.matchWins + second.matchWins));
11846         }
11847     } else {
11848       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11849     }
11850     DisplayTitle(buf);
11851 }
11852
11853 void
11854 SettingsMenuIfReady()
11855 {
11856   if (second.lastPing != second.lastPong) {
11857     DisplayMessage("", _("Waiting for second chess program"));
11858     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11859     return;
11860   }
11861   ThawUI();
11862   DisplayMessage("", "");
11863   SettingsPopUp(&second);
11864 }
11865
11866 int
11867 WaitForSecond(DelayedEventCallback retry)
11868 {
11869     if (second.pr == NULL) {
11870         StartChessProgram(&second);
11871         if (second.protocolVersion == 1) {
11872           retry();
11873         } else {
11874           /* kludge: allow timeout for initial "feature" command */
11875           FreezeUI();
11876           DisplayMessage("", _("Starting second chess program"));
11877           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11878         }
11879         return 1;
11880     }
11881     return 0;
11882 }
11883
11884 void
11885 TwoMachinesEvent P((void))
11886 {
11887     int i;
11888     char buf[MSG_SIZ];
11889     ChessProgramState *onmove;
11890     char *bookHit = NULL;
11891
11892     if (appData.noChessProgram) return;
11893
11894     switch (gameMode) {
11895       case TwoMachinesPlay:
11896         return;
11897       case MachinePlaysWhite:
11898       case MachinePlaysBlack:
11899         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11900             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11901             return;
11902         }
11903         /* fall through */
11904       case BeginningOfGame:
11905       case PlayFromGameFile:
11906       case EndOfGame:
11907         EditGameEvent();
11908         if (gameMode != EditGame) return;
11909         break;
11910       case EditPosition:
11911         EditPositionDone(TRUE);
11912         break;
11913       case AnalyzeMode:
11914       case AnalyzeFile:
11915         ExitAnalyzeMode();
11916         break;
11917       case EditGame:
11918       default:
11919         break;
11920     }
11921
11922 //    forwardMostMove = currentMove;
11923     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11924     ResurrectChessProgram();    /* in case first program isn't running */
11925
11926     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11927     DisplayMessage("", "");
11928     InitChessProgram(&second, FALSE);
11929     SendToProgram("force\n", &second);
11930     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11931       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11932       return;
11933     }
11934     if (startedFromSetupPosition) {
11935         SendBoard(&second, backwardMostMove);
11936     if (appData.debugMode) {
11937         fprintf(debugFP, "Two Machines\n");
11938     }
11939     }
11940     for (i = backwardMostMove; i < forwardMostMove; i++) {
11941         SendMoveToProgram(i, &second);
11942     }
11943
11944     gameMode = TwoMachinesPlay;
11945     pausing = FALSE;
11946     ModeHighlight();
11947     SetGameInfo();
11948     DisplayTwoMachinesTitle();
11949     firstMove = TRUE;
11950     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11951         onmove = &first;
11952     } else {
11953         onmove = &second;
11954     }
11955
11956     SendToProgram(first.computerString, &first);
11957     if (first.sendName) {
11958       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11959       SendToProgram(buf, &first);
11960     }
11961     SendToProgram(second.computerString, &second);
11962     if (second.sendName) {
11963       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11964       SendToProgram(buf, &second);
11965     }
11966
11967     ResetClocks();
11968     if (!first.sendTime || !second.sendTime) {
11969         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11970         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11971     }
11972     if (onmove->sendTime) {
11973       if (onmove->useColors) {
11974         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11975       }
11976       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11977     }
11978     if (onmove->useColors) {
11979       SendToProgram(onmove->twoMachinesColor, onmove);
11980     }
11981     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11982 //    SendToProgram("go\n", onmove);
11983     onmove->maybeThinking = TRUE;
11984     SetMachineThinkingEnables();
11985
11986     StartClocks();
11987
11988     if(bookHit) { // [HGM] book: simulate book reply
11989         static char bookMove[MSG_SIZ]; // a bit generous?
11990
11991         programStats.nodes = programStats.depth = programStats.time =
11992         programStats.score = programStats.got_only_move = 0;
11993         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11994
11995         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11996         strcat(bookMove, bookHit);
11997         savedMessage = bookMove; // args for deferred call
11998         savedState = onmove;
11999         ScheduleDelayedEvent(DeferredBookMove, 1);
12000     }
12001 }
12002
12003 void
12004 TrainingEvent()
12005 {
12006     if (gameMode == Training) {
12007       SetTrainingModeOff();
12008       gameMode = PlayFromGameFile;
12009       DisplayMessage("", _("Training mode off"));
12010     } else {
12011       gameMode = Training;
12012       animateTraining = appData.animate;
12013
12014       /* make sure we are not already at the end of the game */
12015       if (currentMove < forwardMostMove) {
12016         SetTrainingModeOn();
12017         DisplayMessage("", _("Training mode on"));
12018       } else {
12019         gameMode = PlayFromGameFile;
12020         DisplayError(_("Already at end of game"), 0);
12021       }
12022     }
12023     ModeHighlight();
12024 }
12025
12026 void
12027 IcsClientEvent()
12028 {
12029     if (!appData.icsActive) return;
12030     switch (gameMode) {
12031       case IcsPlayingWhite:
12032       case IcsPlayingBlack:
12033       case IcsObserving:
12034       case IcsIdle:
12035       case BeginningOfGame:
12036       case IcsExamining:
12037         return;
12038
12039       case EditGame:
12040         break;
12041
12042       case EditPosition:
12043         EditPositionDone(TRUE);
12044         break;
12045
12046       case AnalyzeMode:
12047       case AnalyzeFile:
12048         ExitAnalyzeMode();
12049         break;
12050
12051       default:
12052         EditGameEvent();
12053         break;
12054     }
12055
12056     gameMode = IcsIdle;
12057     ModeHighlight();
12058     return;
12059 }
12060
12061
12062 void
12063 EditGameEvent()
12064 {
12065     int i;
12066
12067     switch (gameMode) {
12068       case Training:
12069         SetTrainingModeOff();
12070         break;
12071       case MachinePlaysWhite:
12072       case MachinePlaysBlack:
12073       case BeginningOfGame:
12074         SendToProgram("force\n", &first);
12075         SetUserThinkingEnables();
12076         break;
12077       case PlayFromGameFile:
12078         (void) StopLoadGameTimer();
12079         if (gameFileFP != NULL) {
12080             gameFileFP = NULL;
12081         }
12082         break;
12083       case EditPosition:
12084         EditPositionDone(TRUE);
12085         break;
12086       case AnalyzeMode:
12087       case AnalyzeFile:
12088         ExitAnalyzeMode();
12089         SendToProgram("force\n", &first);
12090         break;
12091       case TwoMachinesPlay:
12092         GameEnds(EndOfFile, NULL, GE_PLAYER);
12093         ResurrectChessProgram();
12094         SetUserThinkingEnables();
12095         break;
12096       case EndOfGame:
12097         ResurrectChessProgram();
12098         break;
12099       case IcsPlayingBlack:
12100       case IcsPlayingWhite:
12101         DisplayError(_("Warning: You are still playing a game"), 0);
12102         break;
12103       case IcsObserving:
12104         DisplayError(_("Warning: You are still observing a game"), 0);
12105         break;
12106       case IcsExamining:
12107         DisplayError(_("Warning: You are still examining a game"), 0);
12108         break;
12109       case IcsIdle:
12110         break;
12111       case EditGame:
12112       default:
12113         return;
12114     }
12115
12116     pausing = FALSE;
12117     StopClocks();
12118     first.offeredDraw = second.offeredDraw = 0;
12119
12120     if (gameMode == PlayFromGameFile) {
12121         whiteTimeRemaining = timeRemaining[0][currentMove];
12122         blackTimeRemaining = timeRemaining[1][currentMove];
12123         DisplayTitle("");
12124     }
12125
12126     if (gameMode == MachinePlaysWhite ||
12127         gameMode == MachinePlaysBlack ||
12128         gameMode == TwoMachinesPlay ||
12129         gameMode == EndOfGame) {
12130         i = forwardMostMove;
12131         while (i > currentMove) {
12132             SendToProgram("undo\n", &first);
12133             i--;
12134         }
12135         whiteTimeRemaining = timeRemaining[0][currentMove];
12136         blackTimeRemaining = timeRemaining[1][currentMove];
12137         DisplayBothClocks();
12138         if (whiteFlag || blackFlag) {
12139             whiteFlag = blackFlag = 0;
12140         }
12141         DisplayTitle("");
12142     }
12143
12144     gameMode = EditGame;
12145     ModeHighlight();
12146     SetGameInfo();
12147 }
12148
12149
12150 void
12151 EditPositionEvent()
12152 {
12153     if (gameMode == EditPosition) {
12154         EditGameEvent();
12155         return;
12156     }
12157
12158     EditGameEvent();
12159     if (gameMode != EditGame) return;
12160
12161     gameMode = EditPosition;
12162     ModeHighlight();
12163     SetGameInfo();
12164     if (currentMove > 0)
12165       CopyBoard(boards[0], boards[currentMove]);
12166
12167     blackPlaysFirst = !WhiteOnMove(currentMove);
12168     ResetClocks();
12169     currentMove = forwardMostMove = backwardMostMove = 0;
12170     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12171     DisplayMove(-1);
12172 }
12173
12174 void
12175 ExitAnalyzeMode()
12176 {
12177     /* [DM] icsEngineAnalyze - possible call from other functions */
12178     if (appData.icsEngineAnalyze) {
12179         appData.icsEngineAnalyze = FALSE;
12180
12181         DisplayMessage("",_("Close ICS engine analyze..."));
12182     }
12183     if (first.analysisSupport && first.analyzing) {
12184       SendToProgram("exit\n", &first);
12185       first.analyzing = FALSE;
12186     }
12187     thinkOutput[0] = NULLCHAR;
12188 }
12189
12190 void
12191 EditPositionDone(Boolean fakeRights)
12192 {
12193     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12194
12195     startedFromSetupPosition = TRUE;
12196     InitChessProgram(&first, FALSE);
12197     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12198       boards[0][EP_STATUS] = EP_NONE;
12199       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12200     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12201         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12202         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12203       } else boards[0][CASTLING][2] = NoRights;
12204     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12205         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12206         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12207       } else boards[0][CASTLING][5] = NoRights;
12208     }
12209     SendToProgram("force\n", &first);
12210     if (blackPlaysFirst) {
12211         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12212         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12213         currentMove = forwardMostMove = backwardMostMove = 1;
12214         CopyBoard(boards[1], boards[0]);
12215     } else {
12216         currentMove = forwardMostMove = backwardMostMove = 0;
12217     }
12218     SendBoard(&first, forwardMostMove);
12219     if (appData.debugMode) {
12220         fprintf(debugFP, "EditPosDone\n");
12221     }
12222     DisplayTitle("");
12223     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12224     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12225     gameMode = EditGame;
12226     ModeHighlight();
12227     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12228     ClearHighlights(); /* [AS] */
12229 }
12230
12231 /* Pause for `ms' milliseconds */
12232 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12233 void
12234 TimeDelay(ms)
12235      long ms;
12236 {
12237     TimeMark m1, m2;
12238
12239     GetTimeMark(&m1);
12240     do {
12241         GetTimeMark(&m2);
12242     } while (SubtractTimeMarks(&m2, &m1) < ms);
12243 }
12244
12245 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12246 void
12247 SendMultiLineToICS(buf)
12248      char *buf;
12249 {
12250     char temp[MSG_SIZ+1], *p;
12251     int len;
12252
12253     len = strlen(buf);
12254     if (len > MSG_SIZ)
12255       len = MSG_SIZ;
12256
12257     strncpy(temp, buf, len);
12258     temp[len] = 0;
12259
12260     p = temp;
12261     while (*p) {
12262         if (*p == '\n' || *p == '\r')
12263           *p = ' ';
12264         ++p;
12265     }
12266
12267     strcat(temp, "\n");
12268     SendToICS(temp);
12269     SendToPlayer(temp, strlen(temp));
12270 }
12271
12272 void
12273 SetWhiteToPlayEvent()
12274 {
12275     if (gameMode == EditPosition) {
12276         blackPlaysFirst = FALSE;
12277         DisplayBothClocks();    /* works because currentMove is 0 */
12278     } else if (gameMode == IcsExamining) {
12279         SendToICS(ics_prefix);
12280         SendToICS("tomove white\n");
12281     }
12282 }
12283
12284 void
12285 SetBlackToPlayEvent()
12286 {
12287     if (gameMode == EditPosition) {
12288         blackPlaysFirst = TRUE;
12289         currentMove = 1;        /* kludge */
12290         DisplayBothClocks();
12291         currentMove = 0;
12292     } else if (gameMode == IcsExamining) {
12293         SendToICS(ics_prefix);
12294         SendToICS("tomove black\n");
12295     }
12296 }
12297
12298 void
12299 EditPositionMenuEvent(selection, x, y)
12300      ChessSquare selection;
12301      int x, y;
12302 {
12303     char buf[MSG_SIZ];
12304     ChessSquare piece = boards[0][y][x];
12305
12306     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12307
12308     switch (selection) {
12309       case ClearBoard:
12310         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12311             SendToICS(ics_prefix);
12312             SendToICS("bsetup clear\n");
12313         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12314             SendToICS(ics_prefix);
12315             SendToICS("clearboard\n");
12316         } else {
12317             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12318                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12319                 for (y = 0; y < BOARD_HEIGHT; y++) {
12320                     if (gameMode == IcsExamining) {
12321                         if (boards[currentMove][y][x] != EmptySquare) {
12322                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12323                                     AAA + x, ONE + y);
12324                             SendToICS(buf);
12325                         }
12326                     } else {
12327                         boards[0][y][x] = p;
12328                     }
12329                 }
12330             }
12331         }
12332         if (gameMode == EditPosition) {
12333             DrawPosition(FALSE, boards[0]);
12334         }
12335         break;
12336
12337       case WhitePlay:
12338         SetWhiteToPlayEvent();
12339         break;
12340
12341       case BlackPlay:
12342         SetBlackToPlayEvent();
12343         break;
12344
12345       case EmptySquare:
12346         if (gameMode == IcsExamining) {
12347             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12348             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12349             SendToICS(buf);
12350         } else {
12351             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12352                 if(x == BOARD_LEFT-2) {
12353                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12354                     boards[0][y][1] = 0;
12355                 } else
12356                 if(x == BOARD_RGHT+1) {
12357                     if(y >= gameInfo.holdingsSize) break;
12358                     boards[0][y][BOARD_WIDTH-2] = 0;
12359                 } else break;
12360             }
12361             boards[0][y][x] = EmptySquare;
12362             DrawPosition(FALSE, boards[0]);
12363         }
12364         break;
12365
12366       case PromotePiece:
12367         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12368            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12369             selection = (ChessSquare) (PROMOTED piece);
12370         } else if(piece == EmptySquare) selection = WhiteSilver;
12371         else selection = (ChessSquare)((int)piece - 1);
12372         goto defaultlabel;
12373
12374       case DemotePiece:
12375         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12376            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12377             selection = (ChessSquare) (DEMOTED piece);
12378         } else if(piece == EmptySquare) selection = BlackSilver;
12379         else selection = (ChessSquare)((int)piece + 1);
12380         goto defaultlabel;
12381
12382       case WhiteQueen:
12383       case BlackQueen:
12384         if(gameInfo.variant == VariantShatranj ||
12385            gameInfo.variant == VariantXiangqi  ||
12386            gameInfo.variant == VariantCourier  ||
12387            gameInfo.variant == VariantMakruk     )
12388             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12389         goto defaultlabel;
12390
12391       case WhiteKing:
12392       case BlackKing:
12393         if(gameInfo.variant == VariantXiangqi)
12394             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12395         if(gameInfo.variant == VariantKnightmate)
12396             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12397       default:
12398         defaultlabel:
12399         if (gameMode == IcsExamining) {
12400             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12401             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12402                      PieceToChar(selection), AAA + x, ONE + y);
12403             SendToICS(buf);
12404         } else {
12405             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12406                 int n;
12407                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12408                     n = PieceToNumber(selection - BlackPawn);
12409                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12410                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12411                     boards[0][BOARD_HEIGHT-1-n][1]++;
12412                 } else
12413                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12414                     n = PieceToNumber(selection);
12415                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12416                     boards[0][n][BOARD_WIDTH-1] = selection;
12417                     boards[0][n][BOARD_WIDTH-2]++;
12418                 }
12419             } else
12420             boards[0][y][x] = selection;
12421             DrawPosition(TRUE, boards[0]);
12422         }
12423         break;
12424     }
12425 }
12426
12427
12428 void
12429 DropMenuEvent(selection, x, y)
12430      ChessSquare selection;
12431      int x, y;
12432 {
12433     ChessMove moveType;
12434
12435     switch (gameMode) {
12436       case IcsPlayingWhite:
12437       case MachinePlaysBlack:
12438         if (!WhiteOnMove(currentMove)) {
12439             DisplayMoveError(_("It is Black's turn"));
12440             return;
12441         }
12442         moveType = WhiteDrop;
12443         break;
12444       case IcsPlayingBlack:
12445       case MachinePlaysWhite:
12446         if (WhiteOnMove(currentMove)) {
12447             DisplayMoveError(_("It is White's turn"));
12448             return;
12449         }
12450         moveType = BlackDrop;
12451         break;
12452       case EditGame:
12453         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12454         break;
12455       default:
12456         return;
12457     }
12458
12459     if (moveType == BlackDrop && selection < BlackPawn) {
12460       selection = (ChessSquare) ((int) selection
12461                                  + (int) BlackPawn - (int) WhitePawn);
12462     }
12463     if (boards[currentMove][y][x] != EmptySquare) {
12464         DisplayMoveError(_("That square is occupied"));
12465         return;
12466     }
12467
12468     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12469 }
12470
12471 void
12472 AcceptEvent()
12473 {
12474     /* Accept a pending offer of any kind from opponent */
12475
12476     if (appData.icsActive) {
12477         SendToICS(ics_prefix);
12478         SendToICS("accept\n");
12479     } else if (cmailMsgLoaded) {
12480         if (currentMove == cmailOldMove &&
12481             commentList[cmailOldMove] != NULL &&
12482             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12483                    "Black offers a draw" : "White offers a draw")) {
12484             TruncateGame();
12485             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12486             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12487         } else {
12488             DisplayError(_("There is no pending offer on this move"), 0);
12489             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12490         }
12491     } else {
12492         /* Not used for offers from chess program */
12493     }
12494 }
12495
12496 void
12497 DeclineEvent()
12498 {
12499     /* Decline a pending offer of any kind from opponent */
12500
12501     if (appData.icsActive) {
12502         SendToICS(ics_prefix);
12503         SendToICS("decline\n");
12504     } else if (cmailMsgLoaded) {
12505         if (currentMove == cmailOldMove &&
12506             commentList[cmailOldMove] != NULL &&
12507             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12508                    "Black offers a draw" : "White offers a draw")) {
12509 #ifdef NOTDEF
12510             AppendComment(cmailOldMove, "Draw declined", TRUE);
12511             DisplayComment(cmailOldMove - 1, "Draw declined");
12512 #endif /*NOTDEF*/
12513         } else {
12514             DisplayError(_("There is no pending offer on this move"), 0);
12515         }
12516     } else {
12517         /* Not used for offers from chess program */
12518     }
12519 }
12520
12521 void
12522 RematchEvent()
12523 {
12524     /* Issue ICS rematch command */
12525     if (appData.icsActive) {
12526         SendToICS(ics_prefix);
12527         SendToICS("rematch\n");
12528     }
12529 }
12530
12531 void
12532 CallFlagEvent()
12533 {
12534     /* Call your opponent's flag (claim a win on time) */
12535     if (appData.icsActive) {
12536         SendToICS(ics_prefix);
12537         SendToICS("flag\n");
12538     } else {
12539         switch (gameMode) {
12540           default:
12541             return;
12542           case MachinePlaysWhite:
12543             if (whiteFlag) {
12544                 if (blackFlag)
12545                   GameEnds(GameIsDrawn, "Both players ran out of time",
12546                            GE_PLAYER);
12547                 else
12548                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12549             } else {
12550                 DisplayError(_("Your opponent is not out of time"), 0);
12551             }
12552             break;
12553           case MachinePlaysBlack:
12554             if (blackFlag) {
12555                 if (whiteFlag)
12556                   GameEnds(GameIsDrawn, "Both players ran out of time",
12557                            GE_PLAYER);
12558                 else
12559                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12560             } else {
12561                 DisplayError(_("Your opponent is not out of time"), 0);
12562             }
12563             break;
12564         }
12565     }
12566 }
12567
12568 void
12569 DrawEvent()
12570 {
12571     /* Offer draw or accept pending draw offer from opponent */
12572
12573     if (appData.icsActive) {
12574         /* Note: tournament rules require draw offers to be
12575            made after you make your move but before you punch
12576            your clock.  Currently ICS doesn't let you do that;
12577            instead, you immediately punch your clock after making
12578            a move, but you can offer a draw at any time. */
12579
12580         SendToICS(ics_prefix);
12581         SendToICS("draw\n");
12582         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12583     } else if (cmailMsgLoaded) {
12584         if (currentMove == cmailOldMove &&
12585             commentList[cmailOldMove] != NULL &&
12586             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12587                    "Black offers a draw" : "White offers a draw")) {
12588             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12589             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12590         } else if (currentMove == cmailOldMove + 1) {
12591             char *offer = WhiteOnMove(cmailOldMove) ?
12592               "White offers a draw" : "Black offers a draw";
12593             AppendComment(currentMove, offer, TRUE);
12594             DisplayComment(currentMove - 1, offer);
12595             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12596         } else {
12597             DisplayError(_("You must make your move before offering a draw"), 0);
12598             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12599         }
12600     } else if (first.offeredDraw) {
12601         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12602     } else {
12603         if (first.sendDrawOffers) {
12604             SendToProgram("draw\n", &first);
12605             userOfferedDraw = TRUE;
12606         }
12607     }
12608 }
12609
12610 void
12611 AdjournEvent()
12612 {
12613     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12614
12615     if (appData.icsActive) {
12616         SendToICS(ics_prefix);
12617         SendToICS("adjourn\n");
12618     } else {
12619         /* Currently GNU Chess doesn't offer or accept Adjourns */
12620     }
12621 }
12622
12623
12624 void
12625 AbortEvent()
12626 {
12627     /* Offer Abort or accept pending Abort offer from opponent */
12628
12629     if (appData.icsActive) {
12630         SendToICS(ics_prefix);
12631         SendToICS("abort\n");
12632     } else {
12633         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12634     }
12635 }
12636
12637 void
12638 ResignEvent()
12639 {
12640     /* Resign.  You can do this even if it's not your turn. */
12641
12642     if (appData.icsActive) {
12643         SendToICS(ics_prefix);
12644         SendToICS("resign\n");
12645     } else {
12646         switch (gameMode) {
12647           case MachinePlaysWhite:
12648             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12649             break;
12650           case MachinePlaysBlack:
12651             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12652             break;
12653           case EditGame:
12654             if (cmailMsgLoaded) {
12655                 TruncateGame();
12656                 if (WhiteOnMove(cmailOldMove)) {
12657                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12658                 } else {
12659                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12660                 }
12661                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12662             }
12663             break;
12664           default:
12665             break;
12666         }
12667     }
12668 }
12669
12670
12671 void
12672 StopObservingEvent()
12673 {
12674     /* Stop observing current games */
12675     SendToICS(ics_prefix);
12676     SendToICS("unobserve\n");
12677 }
12678
12679 void
12680 StopExaminingEvent()
12681 {
12682     /* Stop observing current game */
12683     SendToICS(ics_prefix);
12684     SendToICS("unexamine\n");
12685 }
12686
12687 void
12688 ForwardInner(target)
12689      int target;
12690 {
12691     int limit;
12692
12693     if (appData.debugMode)
12694         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12695                 target, currentMove, forwardMostMove);
12696
12697     if (gameMode == EditPosition)
12698       return;
12699
12700     if (gameMode == PlayFromGameFile && !pausing)
12701       PauseEvent();
12702
12703     if (gameMode == IcsExamining && pausing)
12704       limit = pauseExamForwardMostMove;
12705     else
12706       limit = forwardMostMove;
12707
12708     if (target > limit) target = limit;
12709
12710     if (target > 0 && moveList[target - 1][0]) {
12711         int fromX, fromY, toX, toY;
12712         toX = moveList[target - 1][2] - AAA;
12713         toY = moveList[target - 1][3] - ONE;
12714         if (moveList[target - 1][1] == '@') {
12715             if (appData.highlightLastMove) {
12716                 SetHighlights(-1, -1, toX, toY);
12717             }
12718         } else {
12719             fromX = moveList[target - 1][0] - AAA;
12720             fromY = moveList[target - 1][1] - ONE;
12721             if (target == currentMove + 1) {
12722                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12723             }
12724             if (appData.highlightLastMove) {
12725                 SetHighlights(fromX, fromY, toX, toY);
12726             }
12727         }
12728     }
12729     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12730         gameMode == Training || gameMode == PlayFromGameFile ||
12731         gameMode == AnalyzeFile) {
12732         while (currentMove < target) {
12733             SendMoveToProgram(currentMove++, &first);
12734         }
12735     } else {
12736         currentMove = target;
12737     }
12738
12739     if (gameMode == EditGame || gameMode == EndOfGame) {
12740         whiteTimeRemaining = timeRemaining[0][currentMove];
12741         blackTimeRemaining = timeRemaining[1][currentMove];
12742     }
12743     DisplayBothClocks();
12744     DisplayMove(currentMove - 1);
12745     DrawPosition(FALSE, boards[currentMove]);
12746     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12747     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12748         DisplayComment(currentMove - 1, commentList[currentMove]);
12749     }
12750 }
12751
12752
12753 void
12754 ForwardEvent()
12755 {
12756     if (gameMode == IcsExamining && !pausing) {
12757         SendToICS(ics_prefix);
12758         SendToICS("forward\n");
12759     } else {
12760         ForwardInner(currentMove + 1);
12761     }
12762 }
12763
12764 void
12765 ToEndEvent()
12766 {
12767     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12768         /* to optimze, we temporarily turn off analysis mode while we feed
12769          * the remaining moves to the engine. Otherwise we get analysis output
12770          * after each move.
12771          */
12772         if (first.analysisSupport) {
12773           SendToProgram("exit\nforce\n", &first);
12774           first.analyzing = FALSE;
12775         }
12776     }
12777
12778     if (gameMode == IcsExamining && !pausing) {
12779         SendToICS(ics_prefix);
12780         SendToICS("forward 999999\n");
12781     } else {
12782         ForwardInner(forwardMostMove);
12783     }
12784
12785     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12786         /* we have fed all the moves, so reactivate analysis mode */
12787         SendToProgram("analyze\n", &first);
12788         first.analyzing = TRUE;
12789         /*first.maybeThinking = TRUE;*/
12790         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12791     }
12792 }
12793
12794 void
12795 BackwardInner(target)
12796      int target;
12797 {
12798     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12799
12800     if (appData.debugMode)
12801         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12802                 target, currentMove, forwardMostMove);
12803
12804     if (gameMode == EditPosition) return;
12805     if (currentMove <= backwardMostMove) {
12806         ClearHighlights();
12807         DrawPosition(full_redraw, boards[currentMove]);
12808         return;
12809     }
12810     if (gameMode == PlayFromGameFile && !pausing)
12811       PauseEvent();
12812
12813     if (moveList[target][0]) {
12814         int fromX, fromY, toX, toY;
12815         toX = moveList[target][2] - AAA;
12816         toY = moveList[target][3] - ONE;
12817         if (moveList[target][1] == '@') {
12818             if (appData.highlightLastMove) {
12819                 SetHighlights(-1, -1, toX, toY);
12820             }
12821         } else {
12822             fromX = moveList[target][0] - AAA;
12823             fromY = moveList[target][1] - ONE;
12824             if (target == currentMove - 1) {
12825                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12826             }
12827             if (appData.highlightLastMove) {
12828                 SetHighlights(fromX, fromY, toX, toY);
12829             }
12830         }
12831     }
12832     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12833         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12834         while (currentMove > target) {
12835             SendToProgram("undo\n", &first);
12836             currentMove--;
12837         }
12838     } else {
12839         currentMove = target;
12840     }
12841
12842     if (gameMode == EditGame || gameMode == EndOfGame) {
12843         whiteTimeRemaining = timeRemaining[0][currentMove];
12844         blackTimeRemaining = timeRemaining[1][currentMove];
12845     }
12846     DisplayBothClocks();
12847     DisplayMove(currentMove - 1);
12848     DrawPosition(full_redraw, boards[currentMove]);
12849     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12850     // [HGM] PV info: routine tests if comment empty
12851     DisplayComment(currentMove - 1, commentList[currentMove]);
12852 }
12853
12854 void
12855 BackwardEvent()
12856 {
12857     if (gameMode == IcsExamining && !pausing) {
12858         SendToICS(ics_prefix);
12859         SendToICS("backward\n");
12860     } else {
12861         BackwardInner(currentMove - 1);
12862     }
12863 }
12864
12865 void
12866 ToStartEvent()
12867 {
12868     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12869         /* to optimize, we temporarily turn off analysis mode while we undo
12870          * all the moves. Otherwise we get analysis output after each undo.
12871          */
12872         if (first.analysisSupport) {
12873           SendToProgram("exit\nforce\n", &first);
12874           first.analyzing = FALSE;
12875         }
12876     }
12877
12878     if (gameMode == IcsExamining && !pausing) {
12879         SendToICS(ics_prefix);
12880         SendToICS("backward 999999\n");
12881     } else {
12882         BackwardInner(backwardMostMove);
12883     }
12884
12885     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12886         /* we have fed all the moves, so reactivate analysis mode */
12887         SendToProgram("analyze\n", &first);
12888         first.analyzing = TRUE;
12889         /*first.maybeThinking = TRUE;*/
12890         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12891     }
12892 }
12893
12894 void
12895 ToNrEvent(int to)
12896 {
12897   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12898   if (to >= forwardMostMove) to = forwardMostMove;
12899   if (to <= backwardMostMove) to = backwardMostMove;
12900   if (to < currentMove) {
12901     BackwardInner(to);
12902   } else {
12903     ForwardInner(to);
12904   }
12905 }
12906
12907 void
12908 RevertEvent(Boolean annotate)
12909 {
12910     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12911         return;
12912     }
12913     if (gameMode != IcsExamining) {
12914         DisplayError(_("You are not examining a game"), 0);
12915         return;
12916     }
12917     if (pausing) {
12918         DisplayError(_("You can't revert while pausing"), 0);
12919         return;
12920     }
12921     SendToICS(ics_prefix);
12922     SendToICS("revert\n");
12923 }
12924
12925 void
12926 RetractMoveEvent()
12927 {
12928     switch (gameMode) {
12929       case MachinePlaysWhite:
12930       case MachinePlaysBlack:
12931         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12932             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12933             return;
12934         }
12935         if (forwardMostMove < 2) return;
12936         currentMove = forwardMostMove = forwardMostMove - 2;
12937         whiteTimeRemaining = timeRemaining[0][currentMove];
12938         blackTimeRemaining = timeRemaining[1][currentMove];
12939         DisplayBothClocks();
12940         DisplayMove(currentMove - 1);
12941         ClearHighlights();/*!! could figure this out*/
12942         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12943         SendToProgram("remove\n", &first);
12944         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12945         break;
12946
12947       case BeginningOfGame:
12948       default:
12949         break;
12950
12951       case IcsPlayingWhite:
12952       case IcsPlayingBlack:
12953         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12954             SendToICS(ics_prefix);
12955             SendToICS("takeback 2\n");
12956         } else {
12957             SendToICS(ics_prefix);
12958             SendToICS("takeback 1\n");
12959         }
12960         break;
12961     }
12962 }
12963
12964 void
12965 MoveNowEvent()
12966 {
12967     ChessProgramState *cps;
12968
12969     switch (gameMode) {
12970       case MachinePlaysWhite:
12971         if (!WhiteOnMove(forwardMostMove)) {
12972             DisplayError(_("It is your turn"), 0);
12973             return;
12974         }
12975         cps = &first;
12976         break;
12977       case MachinePlaysBlack:
12978         if (WhiteOnMove(forwardMostMove)) {
12979             DisplayError(_("It is your turn"), 0);
12980             return;
12981         }
12982         cps = &first;
12983         break;
12984       case TwoMachinesPlay:
12985         if (WhiteOnMove(forwardMostMove) ==
12986             (first.twoMachinesColor[0] == 'w')) {
12987             cps = &first;
12988         } else {
12989             cps = &second;
12990         }
12991         break;
12992       case BeginningOfGame:
12993       default:
12994         return;
12995     }
12996     SendToProgram("?\n", cps);
12997 }
12998
12999 void
13000 TruncateGameEvent()
13001 {
13002     EditGameEvent();
13003     if (gameMode != EditGame) return;
13004     TruncateGame();
13005 }
13006
13007 void
13008 TruncateGame()
13009 {
13010     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13011     if (forwardMostMove > currentMove) {
13012         if (gameInfo.resultDetails != NULL) {
13013             free(gameInfo.resultDetails);
13014             gameInfo.resultDetails = NULL;
13015             gameInfo.result = GameUnfinished;
13016         }
13017         forwardMostMove = currentMove;
13018         HistorySet(parseList, backwardMostMove, forwardMostMove,
13019                    currentMove-1);
13020     }
13021 }
13022
13023 void
13024 HintEvent()
13025 {
13026     if (appData.noChessProgram) return;
13027     switch (gameMode) {
13028       case MachinePlaysWhite:
13029         if (WhiteOnMove(forwardMostMove)) {
13030             DisplayError(_("Wait until your turn"), 0);
13031             return;
13032         }
13033         break;
13034       case BeginningOfGame:
13035       case MachinePlaysBlack:
13036         if (!WhiteOnMove(forwardMostMove)) {
13037             DisplayError(_("Wait until your turn"), 0);
13038             return;
13039         }
13040         break;
13041       default:
13042         DisplayError(_("No hint available"), 0);
13043         return;
13044     }
13045     SendToProgram("hint\n", &first);
13046     hintRequested = TRUE;
13047 }
13048
13049 void
13050 BookEvent()
13051 {
13052     if (appData.noChessProgram) return;
13053     switch (gameMode) {
13054       case MachinePlaysWhite:
13055         if (WhiteOnMove(forwardMostMove)) {
13056             DisplayError(_("Wait until your turn"), 0);
13057             return;
13058         }
13059         break;
13060       case BeginningOfGame:
13061       case MachinePlaysBlack:
13062         if (!WhiteOnMove(forwardMostMove)) {
13063             DisplayError(_("Wait until your turn"), 0);
13064             return;
13065         }
13066         break;
13067       case EditPosition:
13068         EditPositionDone(TRUE);
13069         break;
13070       case TwoMachinesPlay:
13071         return;
13072       default:
13073         break;
13074     }
13075     SendToProgram("bk\n", &first);
13076     bookOutput[0] = NULLCHAR;
13077     bookRequested = TRUE;
13078 }
13079
13080 void
13081 AboutGameEvent()
13082 {
13083     char *tags = PGNTags(&gameInfo);
13084     TagsPopUp(tags, CmailMsg());
13085     free(tags);
13086 }
13087
13088 /* end button procedures */
13089
13090 void
13091 PrintPosition(fp, move)
13092      FILE *fp;
13093      int move;
13094 {
13095     int i, j;
13096
13097     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13098         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13099             char c = PieceToChar(boards[move][i][j]);
13100             fputc(c == 'x' ? '.' : c, fp);
13101             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13102         }
13103     }
13104     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13105       fprintf(fp, "white to play\n");
13106     else
13107       fprintf(fp, "black to play\n");
13108 }
13109
13110 void
13111 PrintOpponents(fp)
13112      FILE *fp;
13113 {
13114     if (gameInfo.white != NULL) {
13115         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13116     } else {
13117         fprintf(fp, "\n");
13118     }
13119 }
13120
13121 /* Find last component of program's own name, using some heuristics */
13122 void
13123 TidyProgramName(prog, host, buf)
13124      char *prog, *host, buf[MSG_SIZ];
13125 {
13126     char *p, *q;
13127     int local = (strcmp(host, "localhost") == 0);
13128     while (!local && (p = strchr(prog, ';')) != NULL) {
13129         p++;
13130         while (*p == ' ') p++;
13131         prog = p;
13132     }
13133     if (*prog == '"' || *prog == '\'') {
13134         q = strchr(prog + 1, *prog);
13135     } else {
13136         q = strchr(prog, ' ');
13137     }
13138     if (q == NULL) q = prog + strlen(prog);
13139     p = q;
13140     while (p >= prog && *p != '/' && *p != '\\') p--;
13141     p++;
13142     if(p == prog && *p == '"') p++;
13143     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13144     memcpy(buf, p, q - p);
13145     buf[q - p] = NULLCHAR;
13146     if (!local) {
13147         strcat(buf, "@");
13148         strcat(buf, host);
13149     }
13150 }
13151
13152 char *
13153 TimeControlTagValue()
13154 {
13155     char buf[MSG_SIZ];
13156     if (!appData.clockMode) {
13157       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13158     } else if (movesPerSession > 0) {
13159       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13160     } else if (timeIncrement == 0) {
13161       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13162     } else {
13163       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13164     }
13165     return StrSave(buf);
13166 }
13167
13168 void
13169 SetGameInfo()
13170 {
13171     /* This routine is used only for certain modes */
13172     VariantClass v = gameInfo.variant;
13173     ChessMove r = GameUnfinished;
13174     char *p = NULL;
13175
13176     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13177         r = gameInfo.result;
13178         p = gameInfo.resultDetails;
13179         gameInfo.resultDetails = NULL;
13180     }
13181     ClearGameInfo(&gameInfo);
13182     gameInfo.variant = v;
13183
13184     switch (gameMode) {
13185       case MachinePlaysWhite:
13186         gameInfo.event = StrSave( appData.pgnEventHeader );
13187         gameInfo.site = StrSave(HostName());
13188         gameInfo.date = PGNDate();
13189         gameInfo.round = StrSave("-");
13190         gameInfo.white = StrSave(first.tidy);
13191         gameInfo.black = StrSave(UserName());
13192         gameInfo.timeControl = TimeControlTagValue();
13193         break;
13194
13195       case MachinePlaysBlack:
13196         gameInfo.event = StrSave( appData.pgnEventHeader );
13197         gameInfo.site = StrSave(HostName());
13198         gameInfo.date = PGNDate();
13199         gameInfo.round = StrSave("-");
13200         gameInfo.white = StrSave(UserName());
13201         gameInfo.black = StrSave(first.tidy);
13202         gameInfo.timeControl = TimeControlTagValue();
13203         break;
13204
13205       case TwoMachinesPlay:
13206         gameInfo.event = StrSave( appData.pgnEventHeader );
13207         gameInfo.site = StrSave(HostName());
13208         gameInfo.date = PGNDate();
13209         if (matchGame > 0) {
13210             char buf[MSG_SIZ];
13211             snprintf(buf, MSG_SIZ, "%d", matchGame);
13212             gameInfo.round = StrSave(buf);
13213         } else {
13214             gameInfo.round = StrSave("-");
13215         }
13216         if (first.twoMachinesColor[0] == 'w') {
13217             gameInfo.white = StrSave(first.tidy);
13218             gameInfo.black = StrSave(second.tidy);
13219         } else {
13220             gameInfo.white = StrSave(second.tidy);
13221             gameInfo.black = StrSave(first.tidy);
13222         }
13223         gameInfo.timeControl = TimeControlTagValue();
13224         break;
13225
13226       case EditGame:
13227         gameInfo.event = StrSave("Edited game");
13228         gameInfo.site = StrSave(HostName());
13229         gameInfo.date = PGNDate();
13230         gameInfo.round = StrSave("-");
13231         gameInfo.white = StrSave("-");
13232         gameInfo.black = StrSave("-");
13233         gameInfo.result = r;
13234         gameInfo.resultDetails = p;
13235         break;
13236
13237       case EditPosition:
13238         gameInfo.event = StrSave("Edited position");
13239         gameInfo.site = StrSave(HostName());
13240         gameInfo.date = PGNDate();
13241         gameInfo.round = StrSave("-");
13242         gameInfo.white = StrSave("-");
13243         gameInfo.black = StrSave("-");
13244         break;
13245
13246       case IcsPlayingWhite:
13247       case IcsPlayingBlack:
13248       case IcsObserving:
13249       case IcsExamining:
13250         break;
13251
13252       case PlayFromGameFile:
13253         gameInfo.event = StrSave("Game from non-PGN file");
13254         gameInfo.site = StrSave(HostName());
13255         gameInfo.date = PGNDate();
13256         gameInfo.round = StrSave("-");
13257         gameInfo.white = StrSave("?");
13258         gameInfo.black = StrSave("?");
13259         break;
13260
13261       default:
13262         break;
13263     }
13264 }
13265
13266 void
13267 ReplaceComment(index, text)
13268      int index;
13269      char *text;
13270 {
13271     int len;
13272
13273     while (*text == '\n') text++;
13274     len = strlen(text);
13275     while (len > 0 && text[len - 1] == '\n') len--;
13276
13277     if (commentList[index] != NULL)
13278       free(commentList[index]);
13279
13280     if (len == 0) {
13281         commentList[index] = NULL;
13282         return;
13283     }
13284   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13285       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13286       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13287     commentList[index] = (char *) malloc(len + 2);
13288     strncpy(commentList[index], text, len);
13289     commentList[index][len] = '\n';
13290     commentList[index][len + 1] = NULLCHAR;
13291   } else {
13292     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13293     char *p;
13294     commentList[index] = (char *) malloc(len + 7);
13295     safeStrCpy(commentList[index], "{\n", 3);
13296     safeStrCpy(commentList[index]+2, text, len+1);
13297     commentList[index][len+2] = NULLCHAR;
13298     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13299     strcat(commentList[index], "\n}\n");
13300   }
13301 }
13302
13303 void
13304 CrushCRs(text)
13305      char *text;
13306 {
13307   char *p = text;
13308   char *q = text;
13309   char ch;
13310
13311   do {
13312     ch = *p++;
13313     if (ch == '\r') continue;
13314     *q++ = ch;
13315   } while (ch != '\0');
13316 }
13317
13318 void
13319 AppendComment(index, text, addBraces)
13320      int index;
13321      char *text;
13322      Boolean addBraces; // [HGM] braces: tells if we should add {}
13323 {
13324     int oldlen, len;
13325     char *old;
13326
13327 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13328     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13329
13330     CrushCRs(text);
13331     while (*text == '\n') text++;
13332     len = strlen(text);
13333     while (len > 0 && text[len - 1] == '\n') len--;
13334
13335     if (len == 0) return;
13336
13337     if (commentList[index] != NULL) {
13338         old = commentList[index];
13339         oldlen = strlen(old);
13340         while(commentList[index][oldlen-1] ==  '\n')
13341           commentList[index][--oldlen] = NULLCHAR;
13342         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13343         safeStrCpy(commentList[index], old, oldlen);
13344         free(old);
13345         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13346         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13347           if(addBraces) addBraces = FALSE; else { text++; len--; }
13348           while (*text == '\n') { text++; len--; }
13349           commentList[index][--oldlen] = NULLCHAR;
13350       }
13351         if(addBraces) strcat(commentList[index], "\n{\n");
13352         else          strcat(commentList[index], "\n");
13353         strcat(commentList[index], text);
13354         if(addBraces) strcat(commentList[index], "\n}\n");
13355         else          strcat(commentList[index], "\n");
13356     } else {
13357         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13358         if(addBraces)
13359           safeStrCpy(commentList[index], "{\n", 3);
13360         else commentList[index][0] = NULLCHAR;
13361         strcat(commentList[index], text);
13362         strcat(commentList[index], "\n");
13363         if(addBraces) strcat(commentList[index], "}\n");
13364     }
13365 }
13366
13367 static char * FindStr( char * text, char * sub_text )
13368 {
13369     char * result = strstr( text, sub_text );
13370
13371     if( result != NULL ) {
13372         result += strlen( sub_text );
13373     }
13374
13375     return result;
13376 }
13377
13378 /* [AS] Try to extract PV info from PGN comment */
13379 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13380 char *GetInfoFromComment( int index, char * text )
13381 {
13382     char * sep = text;
13383
13384     if( text != NULL && index > 0 ) {
13385         int score = 0;
13386         int depth = 0;
13387         int time = -1, sec = 0, deci;
13388         char * s_eval = FindStr( text, "[%eval " );
13389         char * s_emt = FindStr( text, "[%emt " );
13390
13391         if( s_eval != NULL || s_emt != NULL ) {
13392             /* New style */
13393             char delim;
13394
13395             if( s_eval != NULL ) {
13396                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13397                     return text;
13398                 }
13399
13400                 if( delim != ']' ) {
13401                     return text;
13402                 }
13403             }
13404
13405             if( s_emt != NULL ) {
13406             }
13407                 return text;
13408         }
13409         else {
13410             /* We expect something like: [+|-]nnn.nn/dd */
13411             int score_lo = 0;
13412
13413             if(*text != '{') return text; // [HGM] braces: must be normal comment
13414
13415             sep = strchr( text, '/' );
13416             if( sep == NULL || sep < (text+4) ) {
13417                 return text;
13418             }
13419
13420             time = -1; sec = -1; deci = -1;
13421             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13422                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13423                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13424                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13425                 return text;
13426             }
13427
13428             if( score_lo < 0 || score_lo >= 100 ) {
13429                 return text;
13430             }
13431
13432             if(sec >= 0) time = 600*time + 10*sec; else
13433             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13434
13435             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13436
13437             /* [HGM] PV time: now locate end of PV info */
13438             while( *++sep >= '0' && *sep <= '9'); // strip depth
13439             if(time >= 0)
13440             while( *++sep >= '0' && *sep <= '9'); // strip time
13441             if(sec >= 0)
13442             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13443             if(deci >= 0)
13444             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13445             while(*sep == ' ') sep++;
13446         }
13447
13448         if( depth <= 0 ) {
13449             return text;
13450         }
13451
13452         if( time < 0 ) {
13453             time = -1;
13454         }
13455
13456         pvInfoList[index-1].depth = depth;
13457         pvInfoList[index-1].score = score;
13458         pvInfoList[index-1].time  = 10*time; // centi-sec
13459         if(*sep == '}') *sep = 0; else *--sep = '{';
13460     }
13461     return sep;
13462 }
13463
13464 void
13465 SendToProgram(message, cps)
13466      char *message;
13467      ChessProgramState *cps;
13468 {
13469     int count, outCount, error;
13470     char buf[MSG_SIZ];
13471
13472     if (cps->pr == NULL) return;
13473     Attention(cps);
13474
13475     if (appData.debugMode) {
13476         TimeMark now;
13477         GetTimeMark(&now);
13478         fprintf(debugFP, "%ld >%-6s: %s",
13479                 SubtractTimeMarks(&now, &programStartTime),
13480                 cps->which, message);
13481     }
13482
13483     count = strlen(message);
13484     outCount = OutputToProcess(cps->pr, message, count, &error);
13485     if (outCount < count && !exiting
13486                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13487       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13488         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13489             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13490                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13491                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13492             } else {
13493                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13494             }
13495             gameInfo.resultDetails = StrSave(buf);
13496         }
13497         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13498     }
13499 }
13500
13501 void
13502 ReceiveFromProgram(isr, closure, message, count, error)
13503      InputSourceRef isr;
13504      VOIDSTAR closure;
13505      char *message;
13506      int count;
13507      int error;
13508 {
13509     char *end_str;
13510     char buf[MSG_SIZ];
13511     ChessProgramState *cps = (ChessProgramState *)closure;
13512
13513     if (isr != cps->isr) return; /* Killed intentionally */
13514     if (count <= 0) {
13515         if (count == 0) {
13516             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13517                     cps->which, cps->program);
13518         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13519                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13520                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13521                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13522                 } else {
13523                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13524                 }
13525                 gameInfo.resultDetails = StrSave(buf);
13526             }
13527             RemoveInputSource(cps->isr);
13528             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13529         } else {
13530             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13531                     cps->which, cps->program);
13532             RemoveInputSource(cps->isr);
13533
13534             /* [AS] Program is misbehaving badly... kill it */
13535             if( count == -2 ) {
13536                 DestroyChildProcess( cps->pr, 9 );
13537                 cps->pr = NoProc;
13538             }
13539
13540             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13541         }
13542         return;
13543     }
13544
13545     if ((end_str = strchr(message, '\r')) != NULL)
13546       *end_str = NULLCHAR;
13547     if ((end_str = strchr(message, '\n')) != NULL)
13548       *end_str = NULLCHAR;
13549
13550     if (appData.debugMode) {
13551         TimeMark now; int print = 1;
13552         char *quote = ""; char c; int i;
13553
13554         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13555                 char start = message[0];
13556                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13557                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13558                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13559                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13560                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13561                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13562                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13563                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13564                    sscanf(message, "hint: %c", &c)!=1 && 
13565                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13566                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13567                     print = (appData.engineComments >= 2);
13568                 }
13569                 message[0] = start; // restore original message
13570         }
13571         if(print) {
13572                 GetTimeMark(&now);
13573                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13574                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13575                         quote,
13576                         message);
13577         }
13578     }
13579
13580     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13581     if (appData.icsEngineAnalyze) {
13582         if (strstr(message, "whisper") != NULL ||
13583              strstr(message, "kibitz") != NULL ||
13584             strstr(message, "tellics") != NULL) return;
13585     }
13586
13587     HandleMachineMove(message, cps);
13588 }
13589
13590
13591 void
13592 SendTimeControl(cps, mps, tc, inc, sd, st)
13593      ChessProgramState *cps;
13594      int mps, inc, sd, st;
13595      long tc;
13596 {
13597     char buf[MSG_SIZ];
13598     int seconds;
13599
13600     if( timeControl_2 > 0 ) {
13601         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13602             tc = timeControl_2;
13603         }
13604     }
13605     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13606     inc /= cps->timeOdds;
13607     st  /= cps->timeOdds;
13608
13609     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13610
13611     if (st > 0) {
13612       /* Set exact time per move, normally using st command */
13613       if (cps->stKludge) {
13614         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13615         seconds = st % 60;
13616         if (seconds == 0) {
13617           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13618         } else {
13619           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13620         }
13621       } else {
13622         snprintf(buf, MSG_SIZ, "st %d\n", st);
13623       }
13624     } else {
13625       /* Set conventional or incremental time control, using level command */
13626       if (seconds == 0) {
13627         /* Note old gnuchess bug -- minutes:seconds used to not work.
13628            Fixed in later versions, but still avoid :seconds
13629            when seconds is 0. */
13630         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13631       } else {
13632         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13633                  seconds, inc/1000.);
13634       }
13635     }
13636     SendToProgram(buf, cps);
13637
13638     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13639     /* Orthogonally, limit search to given depth */
13640     if (sd > 0) {
13641       if (cps->sdKludge) {
13642         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13643       } else {
13644         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13645       }
13646       SendToProgram(buf, cps);
13647     }
13648
13649     if(cps->nps > 0) { /* [HGM] nps */
13650         if(cps->supportsNPS == FALSE)
13651           cps->nps = -1; // don't use if engine explicitly says not supported!
13652         else {
13653           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13654           SendToProgram(buf, cps);
13655         }
13656     }
13657 }
13658
13659 ChessProgramState *WhitePlayer()
13660 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13661 {
13662     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13663        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13664         return &second;
13665     return &first;
13666 }
13667
13668 void
13669 SendTimeRemaining(cps, machineWhite)
13670      ChessProgramState *cps;
13671      int /*boolean*/ machineWhite;
13672 {
13673     char message[MSG_SIZ];
13674     long time, otime;
13675
13676     /* Note: this routine must be called when the clocks are stopped
13677        or when they have *just* been set or switched; otherwise
13678        it will be off by the time since the current tick started.
13679     */
13680     if (machineWhite) {
13681         time = whiteTimeRemaining / 10;
13682         otime = blackTimeRemaining / 10;
13683     } else {
13684         time = blackTimeRemaining / 10;
13685         otime = whiteTimeRemaining / 10;
13686     }
13687     /* [HGM] translate opponent's time by time-odds factor */
13688     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13689     if (appData.debugMode) {
13690         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13691     }
13692
13693     if (time <= 0) time = 1;
13694     if (otime <= 0) otime = 1;
13695
13696     snprintf(message, MSG_SIZ, "time %ld\n", time);
13697     SendToProgram(message, cps);
13698
13699     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13700     SendToProgram(message, cps);
13701 }
13702
13703 int
13704 BoolFeature(p, name, loc, cps)
13705      char **p;
13706      char *name;
13707      int *loc;
13708      ChessProgramState *cps;
13709 {
13710   char buf[MSG_SIZ];
13711   int len = strlen(name);
13712   int val;
13713
13714   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13715     (*p) += len + 1;
13716     sscanf(*p, "%d", &val);
13717     *loc = (val != 0);
13718     while (**p && **p != ' ')
13719       (*p)++;
13720     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13721     SendToProgram(buf, cps);
13722     return TRUE;
13723   }
13724   return FALSE;
13725 }
13726
13727 int
13728 IntFeature(p, name, loc, cps)
13729      char **p;
13730      char *name;
13731      int *loc;
13732      ChessProgramState *cps;
13733 {
13734   char buf[MSG_SIZ];
13735   int len = strlen(name);
13736   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13737     (*p) += len + 1;
13738     sscanf(*p, "%d", loc);
13739     while (**p && **p != ' ') (*p)++;
13740     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13741     SendToProgram(buf, cps);
13742     return TRUE;
13743   }
13744   return FALSE;
13745 }
13746
13747 int
13748 StringFeature(p, name, loc, cps)
13749      char **p;
13750      char *name;
13751      char loc[];
13752      ChessProgramState *cps;
13753 {
13754   char buf[MSG_SIZ];
13755   int len = strlen(name);
13756   if (strncmp((*p), name, len) == 0
13757       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13758     (*p) += len + 2;
13759     sscanf(*p, "%[^\"]", loc);
13760     while (**p && **p != '\"') (*p)++;
13761     if (**p == '\"') (*p)++;
13762     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13763     SendToProgram(buf, cps);
13764     return TRUE;
13765   }
13766   return FALSE;
13767 }
13768
13769 int
13770 ParseOption(Option *opt, ChessProgramState *cps)
13771 // [HGM] options: process the string that defines an engine option, and determine
13772 // name, type, default value, and allowed value range
13773 {
13774         char *p, *q, buf[MSG_SIZ];
13775         int n, min = (-1)<<31, max = 1<<31, def;
13776
13777         if(p = strstr(opt->name, " -spin ")) {
13778             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13779             if(max < min) max = min; // enforce consistency
13780             if(def < min) def = min;
13781             if(def > max) def = max;
13782             opt->value = def;
13783             opt->min = min;
13784             opt->max = max;
13785             opt->type = Spin;
13786         } else if((p = strstr(opt->name, " -slider "))) {
13787             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13788             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13789             if(max < min) max = min; // enforce consistency
13790             if(def < min) def = min;
13791             if(def > max) def = max;
13792             opt->value = def;
13793             opt->min = min;
13794             opt->max = max;
13795             opt->type = Spin; // Slider;
13796         } else if((p = strstr(opt->name, " -string "))) {
13797             opt->textValue = p+9;
13798             opt->type = TextBox;
13799         } else if((p = strstr(opt->name, " -file "))) {
13800             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13801             opt->textValue = p+7;
13802             opt->type = TextBox; // FileName;
13803         } else if((p = strstr(opt->name, " -path "))) {
13804             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13805             opt->textValue = p+7;
13806             opt->type = TextBox; // PathName;
13807         } else if(p = strstr(opt->name, " -check ")) {
13808             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13809             opt->value = (def != 0);
13810             opt->type = CheckBox;
13811         } else if(p = strstr(opt->name, " -combo ")) {
13812             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13813             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13814             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13815             opt->value = n = 0;
13816             while(q = StrStr(q, " /// ")) {
13817                 n++; *q = 0;    // count choices, and null-terminate each of them
13818                 q += 5;
13819                 if(*q == '*') { // remember default, which is marked with * prefix
13820                     q++;
13821                     opt->value = n;
13822                 }
13823                 cps->comboList[cps->comboCnt++] = q;
13824             }
13825             cps->comboList[cps->comboCnt++] = NULL;
13826             opt->max = n + 1;
13827             opt->type = ComboBox;
13828         } else if(p = strstr(opt->name, " -button")) {
13829             opt->type = Button;
13830         } else if(p = strstr(opt->name, " -save")) {
13831             opt->type = SaveButton;
13832         } else return FALSE;
13833         *p = 0; // terminate option name
13834         // now look if the command-line options define a setting for this engine option.
13835         if(cps->optionSettings && cps->optionSettings[0])
13836             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13837         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13838           snprintf(buf, MSG_SIZ, "option %s", p);
13839                 if(p = strstr(buf, ",")) *p = 0;
13840                 if(q = strchr(buf, '=')) switch(opt->type) {
13841                     case ComboBox:
13842                         for(n=0; n<opt->max; n++)
13843                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13844                         break;
13845                     case TextBox:
13846                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13847                         break;
13848                     case Spin:
13849                     case CheckBox:
13850                         opt->value = atoi(q+1);
13851                     default:
13852                         break;
13853                 }
13854                 strcat(buf, "\n");
13855                 SendToProgram(buf, cps);
13856         }
13857         return TRUE;
13858 }
13859
13860 void
13861 FeatureDone(cps, val)
13862      ChessProgramState* cps;
13863      int val;
13864 {
13865   DelayedEventCallback cb = GetDelayedEvent();
13866   if ((cb == InitBackEnd3 && cps == &first) ||
13867       (cb == SettingsMenuIfReady && cps == &second) ||
13868       (cb == TwoMachinesEventIfReady && cps == &second)) {
13869     CancelDelayedEvent();
13870     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13871   }
13872   cps->initDone = val;
13873 }
13874
13875 /* Parse feature command from engine */
13876 void
13877 ParseFeatures(args, cps)
13878      char* args;
13879      ChessProgramState *cps;
13880 {
13881   char *p = args;
13882   char *q;
13883   int val;
13884   char buf[MSG_SIZ];
13885
13886   for (;;) {
13887     while (*p == ' ') p++;
13888     if (*p == NULLCHAR) return;
13889
13890     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13891     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13892     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13893     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13894     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13895     if (BoolFeature(&p, "reuse", &val, cps)) {
13896       /* Engine can disable reuse, but can't enable it if user said no */
13897       if (!val) cps->reuse = FALSE;
13898       continue;
13899     }
13900     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13901     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13902       if (gameMode == TwoMachinesPlay) {
13903         DisplayTwoMachinesTitle();
13904       } else {
13905         DisplayTitle("");
13906       }
13907       continue;
13908     }
13909     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13910     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13911     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13912     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13913     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13914     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13915     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13916     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13917     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13918     if (IntFeature(&p, "done", &val, cps)) {
13919       FeatureDone(cps, val);
13920       continue;
13921     }
13922     /* Added by Tord: */
13923     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13924     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13925     /* End of additions by Tord */
13926
13927     /* [HGM] added features: */
13928     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13929     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13930     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13931     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13932     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13933     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13934     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13935         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13936           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13937             SendToProgram(buf, cps);
13938             continue;
13939         }
13940         if(cps->nrOptions >= MAX_OPTIONS) {
13941             cps->nrOptions--;
13942             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13943             DisplayError(buf, 0);
13944         }
13945         continue;
13946     }
13947     /* End of additions by HGM */
13948
13949     /* unknown feature: complain and skip */
13950     q = p;
13951     while (*q && *q != '=') q++;
13952     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13953     SendToProgram(buf, cps);
13954     p = q;
13955     if (*p == '=') {
13956       p++;
13957       if (*p == '\"') {
13958         p++;
13959         while (*p && *p != '\"') p++;
13960         if (*p == '\"') p++;
13961       } else {
13962         while (*p && *p != ' ') p++;
13963       }
13964     }
13965   }
13966
13967 }
13968
13969 void
13970 PeriodicUpdatesEvent(newState)
13971      int newState;
13972 {
13973     if (newState == appData.periodicUpdates)
13974       return;
13975
13976     appData.periodicUpdates=newState;
13977
13978     /* Display type changes, so update it now */
13979 //    DisplayAnalysis();
13980
13981     /* Get the ball rolling again... */
13982     if (newState) {
13983         AnalysisPeriodicEvent(1);
13984         StartAnalysisClock();
13985     }
13986 }
13987
13988 void
13989 PonderNextMoveEvent(newState)
13990      int newState;
13991 {
13992     if (newState == appData.ponderNextMove) return;
13993     if (gameMode == EditPosition) EditPositionDone(TRUE);
13994     if (newState) {
13995         SendToProgram("hard\n", &first);
13996         if (gameMode == TwoMachinesPlay) {
13997             SendToProgram("hard\n", &second);
13998         }
13999     } else {
14000         SendToProgram("easy\n", &first);
14001         thinkOutput[0] = NULLCHAR;
14002         if (gameMode == TwoMachinesPlay) {
14003             SendToProgram("easy\n", &second);
14004         }
14005     }
14006     appData.ponderNextMove = newState;
14007 }
14008
14009 void
14010 NewSettingEvent(option, feature, command, value)
14011      char *command;
14012      int option, value, *feature;
14013 {
14014     char buf[MSG_SIZ];
14015
14016     if (gameMode == EditPosition) EditPositionDone(TRUE);
14017     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14018     if(feature == NULL || *feature) SendToProgram(buf, &first);
14019     if (gameMode == TwoMachinesPlay) {
14020         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14021     }
14022 }
14023
14024 void
14025 ShowThinkingEvent()
14026 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14027 {
14028     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14029     int newState = appData.showThinking
14030         // [HGM] thinking: other features now need thinking output as well
14031         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14032
14033     if (oldState == newState) return;
14034     oldState = newState;
14035     if (gameMode == EditPosition) EditPositionDone(TRUE);
14036     if (oldState) {
14037         SendToProgram("post\n", &first);
14038         if (gameMode == TwoMachinesPlay) {
14039             SendToProgram("post\n", &second);
14040         }
14041     } else {
14042         SendToProgram("nopost\n", &first);
14043         thinkOutput[0] = NULLCHAR;
14044         if (gameMode == TwoMachinesPlay) {
14045             SendToProgram("nopost\n", &second);
14046         }
14047     }
14048 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14049 }
14050
14051 void
14052 AskQuestionEvent(title, question, replyPrefix, which)
14053      char *title; char *question; char *replyPrefix; char *which;
14054 {
14055   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14056   if (pr == NoProc) return;
14057   AskQuestion(title, question, replyPrefix, pr);
14058 }
14059
14060 void
14061 DisplayMove(moveNumber)
14062      int moveNumber;
14063 {
14064     char message[MSG_SIZ];
14065     char res[MSG_SIZ];
14066     char cpThinkOutput[MSG_SIZ];
14067
14068     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14069
14070     if (moveNumber == forwardMostMove - 1 ||
14071         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14072
14073         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14074
14075         if (strchr(cpThinkOutput, '\n')) {
14076             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14077         }
14078     } else {
14079         *cpThinkOutput = NULLCHAR;
14080     }
14081
14082     /* [AS] Hide thinking from human user */
14083     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14084         *cpThinkOutput = NULLCHAR;
14085         if( thinkOutput[0] != NULLCHAR ) {
14086             int i;
14087
14088             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14089                 cpThinkOutput[i] = '.';
14090             }
14091             cpThinkOutput[i] = NULLCHAR;
14092             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14093         }
14094     }
14095
14096     if (moveNumber == forwardMostMove - 1 &&
14097         gameInfo.resultDetails != NULL) {
14098         if (gameInfo.resultDetails[0] == NULLCHAR) {
14099           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14100         } else {
14101           snprintf(res, MSG_SIZ, " {%s} %s",
14102                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14103         }
14104     } else {
14105         res[0] = NULLCHAR;
14106     }
14107
14108     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14109         DisplayMessage(res, cpThinkOutput);
14110     } else {
14111       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14112                 WhiteOnMove(moveNumber) ? " " : ".. ",
14113                 parseList[moveNumber], res);
14114         DisplayMessage(message, cpThinkOutput);
14115     }
14116 }
14117
14118 void
14119 DisplayComment(moveNumber, text)
14120      int moveNumber;
14121      char *text;
14122 {
14123     char title[MSG_SIZ];
14124     char buf[8000]; // comment can be long!
14125     int score, depth;
14126
14127     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14128       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14129     } else {
14130       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14131               WhiteOnMove(moveNumber) ? " " : ".. ",
14132               parseList[moveNumber]);
14133     }
14134     // [HGM] PV info: display PV info together with (or as) comment
14135     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14136       if(text == NULL) text = "";
14137       score = pvInfoList[moveNumber].score;
14138       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14139               depth, (pvInfoList[moveNumber].time+50)/100, text);
14140       text = buf;
14141     }
14142     if (text != NULL && (appData.autoDisplayComment || commentUp))
14143         CommentPopUp(title, text);
14144 }
14145
14146 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14147  * might be busy thinking or pondering.  It can be omitted if your
14148  * gnuchess is configured to stop thinking immediately on any user
14149  * input.  However, that gnuchess feature depends on the FIONREAD
14150  * ioctl, which does not work properly on some flavors of Unix.
14151  */
14152 void
14153 Attention(cps)
14154      ChessProgramState *cps;
14155 {
14156 #if ATTENTION
14157     if (!cps->useSigint) return;
14158     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14159     switch (gameMode) {
14160       case MachinePlaysWhite:
14161       case MachinePlaysBlack:
14162       case TwoMachinesPlay:
14163       case IcsPlayingWhite:
14164       case IcsPlayingBlack:
14165       case AnalyzeMode:
14166       case AnalyzeFile:
14167         /* Skip if we know it isn't thinking */
14168         if (!cps->maybeThinking) return;
14169         if (appData.debugMode)
14170           fprintf(debugFP, "Interrupting %s\n", cps->which);
14171         InterruptChildProcess(cps->pr);
14172         cps->maybeThinking = FALSE;
14173         break;
14174       default:
14175         break;
14176     }
14177 #endif /*ATTENTION*/
14178 }
14179
14180 int
14181 CheckFlags()
14182 {
14183     if (whiteTimeRemaining <= 0) {
14184         if (!whiteFlag) {
14185             whiteFlag = TRUE;
14186             if (appData.icsActive) {
14187                 if (appData.autoCallFlag &&
14188                     gameMode == IcsPlayingBlack && !blackFlag) {
14189                   SendToICS(ics_prefix);
14190                   SendToICS("flag\n");
14191                 }
14192             } else {
14193                 if (blackFlag) {
14194                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14195                 } else {
14196                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14197                     if (appData.autoCallFlag) {
14198                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14199                         return TRUE;
14200                     }
14201                 }
14202             }
14203         }
14204     }
14205     if (blackTimeRemaining <= 0) {
14206         if (!blackFlag) {
14207             blackFlag = TRUE;
14208             if (appData.icsActive) {
14209                 if (appData.autoCallFlag &&
14210                     gameMode == IcsPlayingWhite && !whiteFlag) {
14211                   SendToICS(ics_prefix);
14212                   SendToICS("flag\n");
14213                 }
14214             } else {
14215                 if (whiteFlag) {
14216                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14217                 } else {
14218                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14219                     if (appData.autoCallFlag) {
14220                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14221                         return TRUE;
14222                     }
14223                 }
14224             }
14225         }
14226     }
14227     return FALSE;
14228 }
14229
14230 void
14231 CheckTimeControl()
14232 {
14233     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14234         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14235
14236     /*
14237      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14238      */
14239     if ( !WhiteOnMove(forwardMostMove) ) {
14240         /* White made time control */
14241         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14242         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14243         /* [HGM] time odds: correct new time quota for time odds! */
14244                                             / WhitePlayer()->timeOdds;
14245         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14246     } else {
14247         lastBlack -= blackTimeRemaining;
14248         /* Black made time control */
14249         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14250                                             / WhitePlayer()->other->timeOdds;
14251         lastWhite = whiteTimeRemaining;
14252     }
14253 }
14254
14255 void
14256 DisplayBothClocks()
14257 {
14258     int wom = gameMode == EditPosition ?
14259       !blackPlaysFirst : WhiteOnMove(currentMove);
14260     DisplayWhiteClock(whiteTimeRemaining, wom);
14261     DisplayBlackClock(blackTimeRemaining, !wom);
14262 }
14263
14264
14265 /* Timekeeping seems to be a portability nightmare.  I think everyone
14266    has ftime(), but I'm really not sure, so I'm including some ifdefs
14267    to use other calls if you don't.  Clocks will be less accurate if
14268    you have neither ftime nor gettimeofday.
14269 */
14270
14271 /* VS 2008 requires the #include outside of the function */
14272 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14273 #include <sys/timeb.h>
14274 #endif
14275
14276 /* Get the current time as a TimeMark */
14277 void
14278 GetTimeMark(tm)
14279      TimeMark *tm;
14280 {
14281 #if HAVE_GETTIMEOFDAY
14282
14283     struct timeval timeVal;
14284     struct timezone timeZone;
14285
14286     gettimeofday(&timeVal, &timeZone);
14287     tm->sec = (long) timeVal.tv_sec;
14288     tm->ms = (int) (timeVal.tv_usec / 1000L);
14289
14290 #else /*!HAVE_GETTIMEOFDAY*/
14291 #if HAVE_FTIME
14292
14293 // include <sys/timeb.h> / moved to just above start of function
14294     struct timeb timeB;
14295
14296     ftime(&timeB);
14297     tm->sec = (long) timeB.time;
14298     tm->ms = (int) timeB.millitm;
14299
14300 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14301     tm->sec = (long) time(NULL);
14302     tm->ms = 0;
14303 #endif
14304 #endif
14305 }
14306
14307 /* Return the difference in milliseconds between two
14308    time marks.  We assume the difference will fit in a long!
14309 */
14310 long
14311 SubtractTimeMarks(tm2, tm1)
14312      TimeMark *tm2, *tm1;
14313 {
14314     return 1000L*(tm2->sec - tm1->sec) +
14315            (long) (tm2->ms - tm1->ms);
14316 }
14317
14318
14319 /*
14320  * Code to manage the game clocks.
14321  *
14322  * In tournament play, black starts the clock and then white makes a move.
14323  * We give the human user a slight advantage if he is playing white---the
14324  * clocks don't run until he makes his first move, so it takes zero time.
14325  * Also, we don't account for network lag, so we could get out of sync
14326  * with GNU Chess's clock -- but then, referees are always right.
14327  */
14328
14329 static TimeMark tickStartTM;
14330 static long intendedTickLength;
14331
14332 long
14333 NextTickLength(timeRemaining)
14334      long timeRemaining;
14335 {
14336     long nominalTickLength, nextTickLength;
14337
14338     if (timeRemaining > 0L && timeRemaining <= 10000L)
14339       nominalTickLength = 100L;
14340     else
14341       nominalTickLength = 1000L;
14342     nextTickLength = timeRemaining % nominalTickLength;
14343     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14344
14345     return nextTickLength;
14346 }
14347
14348 /* Adjust clock one minute up or down */
14349 void
14350 AdjustClock(Boolean which, int dir)
14351 {
14352     if(which) blackTimeRemaining += 60000*dir;
14353     else      whiteTimeRemaining += 60000*dir;
14354     DisplayBothClocks();
14355 }
14356
14357 /* Stop clocks and reset to a fresh time control */
14358 void
14359 ResetClocks()
14360 {
14361     (void) StopClockTimer();
14362     if (appData.icsActive) {
14363         whiteTimeRemaining = blackTimeRemaining = 0;
14364     } else if (searchTime) {
14365         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14366         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14367     } else { /* [HGM] correct new time quote for time odds */
14368         whiteTC = blackTC = fullTimeControlString;
14369         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14370         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14371     }
14372     if (whiteFlag || blackFlag) {
14373         DisplayTitle("");
14374         whiteFlag = blackFlag = FALSE;
14375     }
14376     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14377     DisplayBothClocks();
14378 }
14379
14380 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14381
14382 /* Decrement running clock by amount of time that has passed */
14383 void
14384 DecrementClocks()
14385 {
14386     long timeRemaining;
14387     long lastTickLength, fudge;
14388     TimeMark now;
14389
14390     if (!appData.clockMode) return;
14391     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14392
14393     GetTimeMark(&now);
14394
14395     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14396
14397     /* Fudge if we woke up a little too soon */
14398     fudge = intendedTickLength - lastTickLength;
14399     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14400
14401     if (WhiteOnMove(forwardMostMove)) {
14402         if(whiteNPS >= 0) lastTickLength = 0;
14403         timeRemaining = whiteTimeRemaining -= lastTickLength;
14404         if(timeRemaining < 0 && !appData.icsActive) {
14405             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14406             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14407                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14408                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14409             }
14410         }
14411         DisplayWhiteClock(whiteTimeRemaining - fudge,
14412                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14413     } else {
14414         if(blackNPS >= 0) lastTickLength = 0;
14415         timeRemaining = blackTimeRemaining -= lastTickLength;
14416         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14417             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14418             if(suddenDeath) {
14419                 blackStartMove = forwardMostMove;
14420                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14421             }
14422         }
14423         DisplayBlackClock(blackTimeRemaining - fudge,
14424                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14425     }
14426     if (CheckFlags()) return;
14427
14428     tickStartTM = now;
14429     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14430     StartClockTimer(intendedTickLength);
14431
14432     /* if the time remaining has fallen below the alarm threshold, sound the
14433      * alarm. if the alarm has sounded and (due to a takeback or time control
14434      * with increment) the time remaining has increased to a level above the
14435      * threshold, reset the alarm so it can sound again.
14436      */
14437
14438     if (appData.icsActive && appData.icsAlarm) {
14439
14440         /* make sure we are dealing with the user's clock */
14441         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14442                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14443            )) return;
14444
14445         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14446             alarmSounded = FALSE;
14447         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14448             PlayAlarmSound();
14449             alarmSounded = TRUE;
14450         }
14451     }
14452 }
14453
14454
14455 /* A player has just moved, so stop the previously running
14456    clock and (if in clock mode) start the other one.
14457    We redisplay both clocks in case we're in ICS mode, because
14458    ICS gives us an update to both clocks after every move.
14459    Note that this routine is called *after* forwardMostMove
14460    is updated, so the last fractional tick must be subtracted
14461    from the color that is *not* on move now.
14462 */
14463 void
14464 SwitchClocks(int newMoveNr)
14465 {
14466     long lastTickLength;
14467     TimeMark now;
14468     int flagged = FALSE;
14469
14470     GetTimeMark(&now);
14471
14472     if (StopClockTimer() && appData.clockMode) {
14473         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14474         if (!WhiteOnMove(forwardMostMove)) {
14475             if(blackNPS >= 0) lastTickLength = 0;
14476             blackTimeRemaining -= lastTickLength;
14477            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14478 //         if(pvInfoList[forwardMostMove-1].time == -1)
14479                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14480                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14481         } else {
14482            if(whiteNPS >= 0) lastTickLength = 0;
14483            whiteTimeRemaining -= lastTickLength;
14484            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14485 //         if(pvInfoList[forwardMostMove-1].time == -1)
14486                  pvInfoList[forwardMostMove-1].time =
14487                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14488         }
14489         flagged = CheckFlags();
14490     }
14491     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14492     CheckTimeControl();
14493
14494     if (flagged || !appData.clockMode) return;
14495
14496     switch (gameMode) {
14497       case MachinePlaysBlack:
14498       case MachinePlaysWhite:
14499       case BeginningOfGame:
14500         if (pausing) return;
14501         break;
14502
14503       case EditGame:
14504       case PlayFromGameFile:
14505       case IcsExamining:
14506         return;
14507
14508       default:
14509         break;
14510     }
14511
14512     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14513         if(WhiteOnMove(forwardMostMove))
14514              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14515         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14516     }
14517
14518     tickStartTM = now;
14519     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14520       whiteTimeRemaining : blackTimeRemaining);
14521     StartClockTimer(intendedTickLength);
14522 }
14523
14524
14525 /* Stop both clocks */
14526 void
14527 StopClocks()
14528 {
14529     long lastTickLength;
14530     TimeMark now;
14531
14532     if (!StopClockTimer()) return;
14533     if (!appData.clockMode) return;
14534
14535     GetTimeMark(&now);
14536
14537     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14538     if (WhiteOnMove(forwardMostMove)) {
14539         if(whiteNPS >= 0) lastTickLength = 0;
14540         whiteTimeRemaining -= lastTickLength;
14541         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14542     } else {
14543         if(blackNPS >= 0) lastTickLength = 0;
14544         blackTimeRemaining -= lastTickLength;
14545         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14546     }
14547     CheckFlags();
14548 }
14549
14550 /* Start clock of player on move.  Time may have been reset, so
14551    if clock is already running, stop and restart it. */
14552 void
14553 StartClocks()
14554 {
14555     (void) StopClockTimer(); /* in case it was running already */
14556     DisplayBothClocks();
14557     if (CheckFlags()) return;
14558
14559     if (!appData.clockMode) return;
14560     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14561
14562     GetTimeMark(&tickStartTM);
14563     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14564       whiteTimeRemaining : blackTimeRemaining);
14565
14566    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14567     whiteNPS = blackNPS = -1;
14568     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14569        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14570         whiteNPS = first.nps;
14571     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14572        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14573         blackNPS = first.nps;
14574     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14575         whiteNPS = second.nps;
14576     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14577         blackNPS = second.nps;
14578     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14579
14580     StartClockTimer(intendedTickLength);
14581 }
14582
14583 char *
14584 TimeString(ms)
14585      long ms;
14586 {
14587     long second, minute, hour, day;
14588     char *sign = "";
14589     static char buf[32];
14590
14591     if (ms > 0 && ms <= 9900) {
14592       /* convert milliseconds to tenths, rounding up */
14593       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14594
14595       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14596       return buf;
14597     }
14598
14599     /* convert milliseconds to seconds, rounding up */
14600     /* use floating point to avoid strangeness of integer division
14601        with negative dividends on many machines */
14602     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14603
14604     if (second < 0) {
14605         sign = "-";
14606         second = -second;
14607     }
14608
14609     day = second / (60 * 60 * 24);
14610     second = second % (60 * 60 * 24);
14611     hour = second / (60 * 60);
14612     second = second % (60 * 60);
14613     minute = second / 60;
14614     second = second % 60;
14615
14616     if (day > 0)
14617       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14618               sign, day, hour, minute, second);
14619     else if (hour > 0)
14620       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14621     else
14622       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14623
14624     return buf;
14625 }
14626
14627
14628 /*
14629  * This is necessary because some C libraries aren't ANSI C compliant yet.
14630  */
14631 char *
14632 StrStr(string, match)
14633      char *string, *match;
14634 {
14635     int i, length;
14636
14637     length = strlen(match);
14638
14639     for (i = strlen(string) - length; i >= 0; i--, string++)
14640       if (!strncmp(match, string, length))
14641         return string;
14642
14643     return NULL;
14644 }
14645
14646 char *
14647 StrCaseStr(string, match)
14648      char *string, *match;
14649 {
14650     int i, j, length;
14651
14652     length = strlen(match);
14653
14654     for (i = strlen(string) - length; i >= 0; i--, string++) {
14655         for (j = 0; j < length; j++) {
14656             if (ToLower(match[j]) != ToLower(string[j]))
14657               break;
14658         }
14659         if (j == length) return string;
14660     }
14661
14662     return NULL;
14663 }
14664
14665 #ifndef _amigados
14666 int
14667 StrCaseCmp(s1, s2)
14668      char *s1, *s2;
14669 {
14670     char c1, c2;
14671
14672     for (;;) {
14673         c1 = ToLower(*s1++);
14674         c2 = ToLower(*s2++);
14675         if (c1 > c2) return 1;
14676         if (c1 < c2) return -1;
14677         if (c1 == NULLCHAR) return 0;
14678     }
14679 }
14680
14681
14682 int
14683 ToLower(c)
14684      int c;
14685 {
14686     return isupper(c) ? tolower(c) : c;
14687 }
14688
14689
14690 int
14691 ToUpper(c)
14692      int c;
14693 {
14694     return islower(c) ? toupper(c) : c;
14695 }
14696 #endif /* !_amigados    */
14697
14698 char *
14699 StrSave(s)
14700      char *s;
14701 {
14702   char *ret;
14703
14704   if ((ret = (char *) malloc(strlen(s) + 1)))
14705     {
14706       safeStrCpy(ret, s, strlen(s)+1);
14707     }
14708   return ret;
14709 }
14710
14711 char *
14712 StrSavePtr(s, savePtr)
14713      char *s, **savePtr;
14714 {
14715     if (*savePtr) {
14716         free(*savePtr);
14717     }
14718     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14719       safeStrCpy(*savePtr, s, strlen(s)+1);
14720     }
14721     return(*savePtr);
14722 }
14723
14724 char *
14725 PGNDate()
14726 {
14727     time_t clock;
14728     struct tm *tm;
14729     char buf[MSG_SIZ];
14730
14731     clock = time((time_t *)NULL);
14732     tm = localtime(&clock);
14733     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14734             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14735     return StrSave(buf);
14736 }
14737
14738
14739 char *
14740 PositionToFEN(move, overrideCastling)
14741      int move;
14742      char *overrideCastling;
14743 {
14744     int i, j, fromX, fromY, toX, toY;
14745     int whiteToPlay;
14746     char buf[128];
14747     char *p, *q;
14748     int emptycount;
14749     ChessSquare piece;
14750
14751     whiteToPlay = (gameMode == EditPosition) ?
14752       !blackPlaysFirst : (move % 2 == 0);
14753     p = buf;
14754
14755     /* Piece placement data */
14756     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14757         emptycount = 0;
14758         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14759             if (boards[move][i][j] == EmptySquare) {
14760                 emptycount++;
14761             } else { ChessSquare piece = boards[move][i][j];
14762                 if (emptycount > 0) {
14763                     if(emptycount<10) /* [HGM] can be >= 10 */
14764                         *p++ = '0' + emptycount;
14765                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14766                     emptycount = 0;
14767                 }
14768                 if(PieceToChar(piece) == '+') {
14769                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14770                     *p++ = '+';
14771                     piece = (ChessSquare)(DEMOTED piece);
14772                 }
14773                 *p++ = PieceToChar(piece);
14774                 if(p[-1] == '~') {
14775                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14776                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14777                     *p++ = '~';
14778                 }
14779             }
14780         }
14781         if (emptycount > 0) {
14782             if(emptycount<10) /* [HGM] can be >= 10 */
14783                 *p++ = '0' + emptycount;
14784             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14785             emptycount = 0;
14786         }
14787         *p++ = '/';
14788     }
14789     *(p - 1) = ' ';
14790
14791     /* [HGM] print Crazyhouse or Shogi holdings */
14792     if( gameInfo.holdingsWidth ) {
14793         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14794         q = p;
14795         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14796             piece = boards[move][i][BOARD_WIDTH-1];
14797             if( piece != EmptySquare )
14798               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14799                   *p++ = PieceToChar(piece);
14800         }
14801         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14802             piece = boards[move][BOARD_HEIGHT-i-1][0];
14803             if( piece != EmptySquare )
14804               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14805                   *p++ = PieceToChar(piece);
14806         }
14807
14808         if( q == p ) *p++ = '-';
14809         *p++ = ']';
14810         *p++ = ' ';
14811     }
14812
14813     /* Active color */
14814     *p++ = whiteToPlay ? 'w' : 'b';
14815     *p++ = ' ';
14816
14817   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14818     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14819   } else {
14820   if(nrCastlingRights) {
14821      q = p;
14822      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14823        /* [HGM] write directly from rights */
14824            if(boards[move][CASTLING][2] != NoRights &&
14825               boards[move][CASTLING][0] != NoRights   )
14826                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14827            if(boards[move][CASTLING][2] != NoRights &&
14828               boards[move][CASTLING][1] != NoRights   )
14829                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14830            if(boards[move][CASTLING][5] != NoRights &&
14831               boards[move][CASTLING][3] != NoRights   )
14832                 *p++ = boards[move][CASTLING][3] + AAA;
14833            if(boards[move][CASTLING][5] != NoRights &&
14834               boards[move][CASTLING][4] != NoRights   )
14835                 *p++ = boards[move][CASTLING][4] + AAA;
14836      } else {
14837
14838         /* [HGM] write true castling rights */
14839         if( nrCastlingRights == 6 ) {
14840             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14841                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14842             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14843                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14844             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14845                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14846             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14847                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14848         }
14849      }
14850      if (q == p) *p++ = '-'; /* No castling rights */
14851      *p++ = ' ';
14852   }
14853
14854   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14855      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14856     /* En passant target square */
14857     if (move > backwardMostMove) {
14858         fromX = moveList[move - 1][0] - AAA;
14859         fromY = moveList[move - 1][1] - ONE;
14860         toX = moveList[move - 1][2] - AAA;
14861         toY = moveList[move - 1][3] - ONE;
14862         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14863             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14864             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14865             fromX == toX) {
14866             /* 2-square pawn move just happened */
14867             *p++ = toX + AAA;
14868             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14869         } else {
14870             *p++ = '-';
14871         }
14872     } else if(move == backwardMostMove) {
14873         // [HGM] perhaps we should always do it like this, and forget the above?
14874         if((signed char)boards[move][EP_STATUS] >= 0) {
14875             *p++ = boards[move][EP_STATUS] + AAA;
14876             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14877         } else {
14878             *p++ = '-';
14879         }
14880     } else {
14881         *p++ = '-';
14882     }
14883     *p++ = ' ';
14884   }
14885   }
14886
14887     /* [HGM] find reversible plies */
14888     {   int i = 0, j=move;
14889
14890         if (appData.debugMode) { int k;
14891             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14892             for(k=backwardMostMove; k<=forwardMostMove; k++)
14893                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14894
14895         }
14896
14897         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14898         if( j == backwardMostMove ) i += initialRulePlies;
14899         sprintf(p, "%d ", i);
14900         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14901     }
14902     /* Fullmove number */
14903     sprintf(p, "%d", (move / 2) + 1);
14904
14905     return StrSave(buf);
14906 }
14907
14908 Boolean
14909 ParseFEN(board, blackPlaysFirst, fen)
14910     Board board;
14911      int *blackPlaysFirst;
14912      char *fen;
14913 {
14914     int i, j;
14915     char *p, c;
14916     int emptycount;
14917     ChessSquare piece;
14918
14919     p = fen;
14920
14921     /* [HGM] by default clear Crazyhouse holdings, if present */
14922     if(gameInfo.holdingsWidth) {
14923        for(i=0; i<BOARD_HEIGHT; i++) {
14924            board[i][0]             = EmptySquare; /* black holdings */
14925            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14926            board[i][1]             = (ChessSquare) 0; /* black counts */
14927            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14928        }
14929     }
14930
14931     /* Piece placement data */
14932     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14933         j = 0;
14934         for (;;) {
14935             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14936                 if (*p == '/') p++;
14937                 emptycount = gameInfo.boardWidth - j;
14938                 while (emptycount--)
14939                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14940                 break;
14941 #if(BOARD_FILES >= 10)
14942             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14943                 p++; emptycount=10;
14944                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14945                 while (emptycount--)
14946                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14947 #endif
14948             } else if (isdigit(*p)) {
14949                 emptycount = *p++ - '0';
14950                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14951                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14952                 while (emptycount--)
14953                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14954             } else if (*p == '+' || isalpha(*p)) {
14955                 if (j >= gameInfo.boardWidth) return FALSE;
14956                 if(*p=='+') {
14957                     piece = CharToPiece(*++p);
14958                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14959                     piece = (ChessSquare) (PROMOTED piece ); p++;
14960                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14961                 } else piece = CharToPiece(*p++);
14962
14963                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14964                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14965                     piece = (ChessSquare) (PROMOTED piece);
14966                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14967                     p++;
14968                 }
14969                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14970             } else {
14971                 return FALSE;
14972             }
14973         }
14974     }
14975     while (*p == '/' || *p == ' ') p++;
14976
14977     /* [HGM] look for Crazyhouse holdings here */
14978     while(*p==' ') p++;
14979     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14980         if(*p == '[') p++;
14981         if(*p == '-' ) p++; /* empty holdings */ else {
14982             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14983             /* if we would allow FEN reading to set board size, we would   */
14984             /* have to add holdings and shift the board read so far here   */
14985             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14986                 p++;
14987                 if((int) piece >= (int) BlackPawn ) {
14988                     i = (int)piece - (int)BlackPawn;
14989                     i = PieceToNumber((ChessSquare)i);
14990                     if( i >= gameInfo.holdingsSize ) return FALSE;
14991                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14992                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14993                 } else {
14994                     i = (int)piece - (int)WhitePawn;
14995                     i = PieceToNumber((ChessSquare)i);
14996                     if( i >= gameInfo.holdingsSize ) return FALSE;
14997                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14998                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14999                 }
15000             }
15001         }
15002         if(*p == ']') p++;
15003     }
15004
15005     while(*p == ' ') p++;
15006
15007     /* Active color */
15008     c = *p++;
15009     if(appData.colorNickNames) {
15010       if( c == appData.colorNickNames[0] ) c = 'w'; else
15011       if( c == appData.colorNickNames[1] ) c = 'b';
15012     }
15013     switch (c) {
15014       case 'w':
15015         *blackPlaysFirst = FALSE;
15016         break;
15017       case 'b':
15018         *blackPlaysFirst = TRUE;
15019         break;
15020       default:
15021         return FALSE;
15022     }
15023
15024     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15025     /* return the extra info in global variiables             */
15026
15027     /* set defaults in case FEN is incomplete */
15028     board[EP_STATUS] = EP_UNKNOWN;
15029     for(i=0; i<nrCastlingRights; i++ ) {
15030         board[CASTLING][i] =
15031             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15032     }   /* assume possible unless obviously impossible */
15033     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15034     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15035     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15036                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15037     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15038     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15039     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15040                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15041     FENrulePlies = 0;
15042
15043     while(*p==' ') p++;
15044     if(nrCastlingRights) {
15045       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15046           /* castling indicator present, so default becomes no castlings */
15047           for(i=0; i<nrCastlingRights; i++ ) {
15048                  board[CASTLING][i] = NoRights;
15049           }
15050       }
15051       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15052              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15053              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15054              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15055         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15056
15057         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15058             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15059             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15060         }
15061         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15062             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15063         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15064                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15065         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15066                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15067         switch(c) {
15068           case'K':
15069               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15070               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15071               board[CASTLING][2] = whiteKingFile;
15072               break;
15073           case'Q':
15074               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15075               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15076               board[CASTLING][2] = whiteKingFile;
15077               break;
15078           case'k':
15079               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15080               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15081               board[CASTLING][5] = blackKingFile;
15082               break;
15083           case'q':
15084               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15085               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15086               board[CASTLING][5] = blackKingFile;
15087           case '-':
15088               break;
15089           default: /* FRC castlings */
15090               if(c >= 'a') { /* black rights */
15091                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15092                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15093                   if(i == BOARD_RGHT) break;
15094                   board[CASTLING][5] = i;
15095                   c -= AAA;
15096                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15097                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15098                   if(c > i)
15099                       board[CASTLING][3] = c;
15100                   else
15101                       board[CASTLING][4] = c;
15102               } else { /* white rights */
15103                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15104                     if(board[0][i] == WhiteKing) break;
15105                   if(i == BOARD_RGHT) break;
15106                   board[CASTLING][2] = i;
15107                   c -= AAA - 'a' + 'A';
15108                   if(board[0][c] >= WhiteKing) break;
15109                   if(c > i)
15110                       board[CASTLING][0] = c;
15111                   else
15112                       board[CASTLING][1] = c;
15113               }
15114         }
15115       }
15116       for(i=0; i<nrCastlingRights; i++)
15117         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15118     if (appData.debugMode) {
15119         fprintf(debugFP, "FEN castling rights:");
15120         for(i=0; i<nrCastlingRights; i++)
15121         fprintf(debugFP, " %d", board[CASTLING][i]);
15122         fprintf(debugFP, "\n");
15123     }
15124
15125       while(*p==' ') p++;
15126     }
15127
15128     /* read e.p. field in games that know e.p. capture */
15129     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15130        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15131       if(*p=='-') {
15132         p++; board[EP_STATUS] = EP_NONE;
15133       } else {
15134          char c = *p++ - AAA;
15135
15136          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15137          if(*p >= '0' && *p <='9') p++;
15138          board[EP_STATUS] = c;
15139       }
15140     }
15141
15142
15143     if(sscanf(p, "%d", &i) == 1) {
15144         FENrulePlies = i; /* 50-move ply counter */
15145         /* (The move number is still ignored)    */
15146     }
15147
15148     return TRUE;
15149 }
15150
15151 void
15152 EditPositionPasteFEN(char *fen)
15153 {
15154   if (fen != NULL) {
15155     Board initial_position;
15156
15157     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15158       DisplayError(_("Bad FEN position in clipboard"), 0);
15159       return ;
15160     } else {
15161       int savedBlackPlaysFirst = blackPlaysFirst;
15162       EditPositionEvent();
15163       blackPlaysFirst = savedBlackPlaysFirst;
15164       CopyBoard(boards[0], initial_position);
15165       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15166       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15167       DisplayBothClocks();
15168       DrawPosition(FALSE, boards[currentMove]);
15169     }
15170   }
15171 }
15172
15173 static char cseq[12] = "\\   ";
15174
15175 Boolean set_cont_sequence(char *new_seq)
15176 {
15177     int len;
15178     Boolean ret;
15179
15180     // handle bad attempts to set the sequence
15181         if (!new_seq)
15182                 return 0; // acceptable error - no debug
15183
15184     len = strlen(new_seq);
15185     ret = (len > 0) && (len < sizeof(cseq));
15186     if (ret)
15187       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15188     else if (appData.debugMode)
15189       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15190     return ret;
15191 }
15192
15193 /*
15194     reformat a source message so words don't cross the width boundary.  internal
15195     newlines are not removed.  returns the wrapped size (no null character unless
15196     included in source message).  If dest is NULL, only calculate the size required
15197     for the dest buffer.  lp argument indicats line position upon entry, and it's
15198     passed back upon exit.
15199 */
15200 int wrap(char *dest, char *src, int count, int width, int *lp)
15201 {
15202     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15203
15204     cseq_len = strlen(cseq);
15205     old_line = line = *lp;
15206     ansi = len = clen = 0;
15207
15208     for (i=0; i < count; i++)
15209     {
15210         if (src[i] == '\033')
15211             ansi = 1;
15212
15213         // if we hit the width, back up
15214         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15215         {
15216             // store i & len in case the word is too long
15217             old_i = i, old_len = len;
15218
15219             // find the end of the last word
15220             while (i && src[i] != ' ' && src[i] != '\n')
15221             {
15222                 i--;
15223                 len--;
15224             }
15225
15226             // word too long?  restore i & len before splitting it
15227             if ((old_i-i+clen) >= width)
15228             {
15229                 i = old_i;
15230                 len = old_len;
15231             }
15232
15233             // extra space?
15234             if (i && src[i-1] == ' ')
15235                 len--;
15236
15237             if (src[i] != ' ' && src[i] != '\n')
15238             {
15239                 i--;
15240                 if (len)
15241                     len--;
15242             }
15243
15244             // now append the newline and continuation sequence
15245             if (dest)
15246                 dest[len] = '\n';
15247             len++;
15248             if (dest)
15249                 strncpy(dest+len, cseq, cseq_len);
15250             len += cseq_len;
15251             line = cseq_len;
15252             clen = cseq_len;
15253             continue;
15254         }
15255
15256         if (dest)
15257             dest[len] = src[i];
15258         len++;
15259         if (!ansi)
15260             line++;
15261         if (src[i] == '\n')
15262             line = 0;
15263         if (src[i] == 'm')
15264             ansi = 0;
15265     }
15266     if (dest && appData.debugMode)
15267     {
15268         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15269             count, width, line, len, *lp);
15270         show_bytes(debugFP, src, count);
15271         fprintf(debugFP, "\ndest: ");
15272         show_bytes(debugFP, dest, len);
15273         fprintf(debugFP, "\n");
15274     }
15275     *lp = dest ? line : old_line;
15276
15277     return len;
15278 }
15279
15280 // [HGM] vari: routines for shelving variations
15281
15282 void
15283 PushTail(int firstMove, int lastMove)
15284 {
15285         int i, j, nrMoves = lastMove - firstMove;
15286
15287         if(appData.icsActive) { // only in local mode
15288                 forwardMostMove = currentMove; // mimic old ICS behavior
15289                 return;
15290         }
15291         if(storedGames >= MAX_VARIATIONS-1) return;
15292
15293         // push current tail of game on stack
15294         savedResult[storedGames] = gameInfo.result;
15295         savedDetails[storedGames] = gameInfo.resultDetails;
15296         gameInfo.resultDetails = NULL;
15297         savedFirst[storedGames] = firstMove;
15298         savedLast [storedGames] = lastMove;
15299         savedFramePtr[storedGames] = framePtr;
15300         framePtr -= nrMoves; // reserve space for the boards
15301         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15302             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15303             for(j=0; j<MOVE_LEN; j++)
15304                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15305             for(j=0; j<2*MOVE_LEN; j++)
15306                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15307             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15308             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15309             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15310             pvInfoList[firstMove+i-1].depth = 0;
15311             commentList[framePtr+i] = commentList[firstMove+i];
15312             commentList[firstMove+i] = NULL;
15313         }
15314
15315         storedGames++;
15316         forwardMostMove = firstMove; // truncate game so we can start variation
15317         if(storedGames == 1) GreyRevert(FALSE);
15318 }
15319
15320 Boolean
15321 PopTail(Boolean annotate)
15322 {
15323         int i, j, nrMoves;
15324         char buf[8000], moveBuf[20];
15325
15326         if(appData.icsActive) return FALSE; // only in local mode
15327         if(!storedGames) return FALSE; // sanity
15328         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15329
15330         storedGames--;
15331         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15332         nrMoves = savedLast[storedGames] - currentMove;
15333         if(annotate) {
15334                 int cnt = 10;
15335                 if(!WhiteOnMove(currentMove))
15336                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15337                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15338                 for(i=currentMove; i<forwardMostMove; i++) {
15339                         if(WhiteOnMove(i))
15340                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15341                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15342                         strcat(buf, moveBuf);
15343                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15344                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15345                 }
15346                 strcat(buf, ")");
15347         }
15348         for(i=1; i<=nrMoves; i++) { // copy last variation back
15349             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15350             for(j=0; j<MOVE_LEN; j++)
15351                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15352             for(j=0; j<2*MOVE_LEN; j++)
15353                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15354             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15355             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15356             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15357             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15358             commentList[currentMove+i] = commentList[framePtr+i];
15359             commentList[framePtr+i] = NULL;
15360         }
15361         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15362         framePtr = savedFramePtr[storedGames];
15363         gameInfo.result = savedResult[storedGames];
15364         if(gameInfo.resultDetails != NULL) {
15365             free(gameInfo.resultDetails);
15366       }
15367         gameInfo.resultDetails = savedDetails[storedGames];
15368         forwardMostMove = currentMove + nrMoves;
15369         if(storedGames == 0) GreyRevert(TRUE);
15370         return TRUE;
15371 }
15372
15373 void
15374 CleanupTail()
15375 {       // remove all shelved variations
15376         int i;
15377         for(i=0; i<storedGames; i++) {
15378             if(savedDetails[i])
15379                 free(savedDetails[i]);
15380             savedDetails[i] = NULL;
15381         }
15382         for(i=framePtr; i<MAX_MOVES; i++) {
15383                 if(commentList[i]) free(commentList[i]);
15384                 commentList[i] = NULL;
15385         }
15386         framePtr = MAX_MOVES-1;
15387         storedGames = 0;
15388 }
15389
15390 void
15391 LoadVariation(int index, char *text)
15392 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15393         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15394         int level = 0, move;
15395
15396         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15397         // first find outermost bracketing variation
15398         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15399             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15400                 if(*p == '{') wait = '}'; else
15401                 if(*p == '[') wait = ']'; else
15402                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15403                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15404             }
15405             if(*p == wait) wait = NULLCHAR; // closing ]} found
15406             p++;
15407         }
15408         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15409         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15410         end[1] = NULLCHAR; // clip off comment beyond variation
15411         ToNrEvent(currentMove-1);
15412         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15413         // kludge: use ParsePV() to append variation to game
15414         move = currentMove;
15415         ParsePV(start, TRUE);
15416         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15417         ClearPremoveHighlights();
15418         CommentPopDown();
15419         ToNrEvent(currentMove+1);
15420 }
15421