Make safeStrCpy safe
[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 || x < 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         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7492         DisplayNote(message + 9);
7493         return;
7494     }
7495     if (!strncmp(message, "tellusererror ", 14)) {
7496         cps->userError = 1;
7497         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7498         DisplayError(message + 14, 0);
7499         return;
7500     }
7501     if (!strncmp(message, "tellopponent ", 13)) {
7502       if (appData.icsActive) {
7503         if (loggedOn) {
7504           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7505           SendToICS(buf1);
7506         }
7507       } else {
7508         DisplayNote(message + 13);
7509       }
7510       return;
7511     }
7512     if (!strncmp(message, "tellothers ", 11)) {
7513       if (appData.icsActive) {
7514         if (loggedOn) {
7515           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7516           SendToICS(buf1);
7517         }
7518       }
7519       return;
7520     }
7521     if (!strncmp(message, "tellall ", 8)) {
7522       if (appData.icsActive) {
7523         if (loggedOn) {
7524           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7525           SendToICS(buf1);
7526         }
7527       } else {
7528         DisplayNote(message + 8);
7529       }
7530       return;
7531     }
7532     if (strncmp(message, "warning", 7) == 0) {
7533         /* Undocumented feature, use tellusererror in new code */
7534         DisplayError(message, 0);
7535         return;
7536     }
7537     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7538         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7539         strcat(realname, " query");
7540         AskQuestion(realname, buf2, buf1, cps->pr);
7541         return;
7542     }
7543     /* Commands from the engine directly to ICS.  We don't allow these to be
7544      *  sent until we are logged on. Crafty kibitzes have been known to
7545      *  interfere with the login process.
7546      */
7547     if (loggedOn) {
7548         if (!strncmp(message, "tellics ", 8)) {
7549             SendToICS(message + 8);
7550             SendToICS("\n");
7551             return;
7552         }
7553         if (!strncmp(message, "tellicsnoalias ", 15)) {
7554             SendToICS(ics_prefix);
7555             SendToICS(message + 15);
7556             SendToICS("\n");
7557             return;
7558         }
7559         /* The following are for backward compatibility only */
7560         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7561             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7562             SendToICS(ics_prefix);
7563             SendToICS(message);
7564             SendToICS("\n");
7565             return;
7566         }
7567     }
7568     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7569         return;
7570     }
7571     /*
7572      * If the move is illegal, cancel it and redraw the board.
7573      * Also deal with other error cases.  Matching is rather loose
7574      * here to accommodate engines written before the spec.
7575      */
7576     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7577         strncmp(message, "Error", 5) == 0) {
7578         if (StrStr(message, "name") ||
7579             StrStr(message, "rating") || StrStr(message, "?") ||
7580             StrStr(message, "result") || StrStr(message, "board") ||
7581             StrStr(message, "bk") || StrStr(message, "computer") ||
7582             StrStr(message, "variant") || StrStr(message, "hint") ||
7583             StrStr(message, "random") || StrStr(message, "depth") ||
7584             StrStr(message, "accepted")) {
7585             return;
7586         }
7587         if (StrStr(message, "protover")) {
7588           /* Program is responding to input, so it's apparently done
7589              initializing, and this error message indicates it is
7590              protocol version 1.  So we don't need to wait any longer
7591              for it to initialize and send feature commands. */
7592           FeatureDone(cps, 1);
7593           cps->protocolVersion = 1;
7594           return;
7595         }
7596         cps->maybeThinking = FALSE;
7597
7598         if (StrStr(message, "draw")) {
7599             /* Program doesn't have "draw" command */
7600             cps->sendDrawOffers = 0;
7601             return;
7602         }
7603         if (cps->sendTime != 1 &&
7604             (StrStr(message, "time") || StrStr(message, "otim"))) {
7605           /* Program apparently doesn't have "time" or "otim" command */
7606           cps->sendTime = 0;
7607           return;
7608         }
7609         if (StrStr(message, "analyze")) {
7610             cps->analysisSupport = FALSE;
7611             cps->analyzing = FALSE;
7612             Reset(FALSE, TRUE);
7613             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7614             DisplayError(buf2, 0);
7615             return;
7616         }
7617         if (StrStr(message, "(no matching move)st")) {
7618           /* Special kludge for GNU Chess 4 only */
7619           cps->stKludge = TRUE;
7620           SendTimeControl(cps, movesPerSession, timeControl,
7621                           timeIncrement, appData.searchDepth,
7622                           searchTime);
7623           return;
7624         }
7625         if (StrStr(message, "(no matching move)sd")) {
7626           /* Special kludge for GNU Chess 4 only */
7627           cps->sdKludge = TRUE;
7628           SendTimeControl(cps, movesPerSession, timeControl,
7629                           timeIncrement, appData.searchDepth,
7630                           searchTime);
7631           return;
7632         }
7633         if (!StrStr(message, "llegal")) {
7634             return;
7635         }
7636         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7637             gameMode == IcsIdle) return;
7638         if (forwardMostMove <= backwardMostMove) return;
7639         if (pausing) PauseEvent();
7640       if(appData.forceIllegal) {
7641             // [HGM] illegal: machine refused move; force position after move into it
7642           SendToProgram("force\n", cps);
7643           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7644                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7645                 // when black is to move, while there might be nothing on a2 or black
7646                 // might already have the move. So send the board as if white has the move.
7647                 // But first we must change the stm of the engine, as it refused the last move
7648                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7649                 if(WhiteOnMove(forwardMostMove)) {
7650                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7651                     SendBoard(cps, forwardMostMove); // kludgeless board
7652                 } else {
7653                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7654                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7655                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7656                 }
7657           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7658             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7659                  gameMode == TwoMachinesPlay)
7660               SendToProgram("go\n", cps);
7661             return;
7662       } else
7663         if (gameMode == PlayFromGameFile) {
7664             /* Stop reading this game file */
7665             gameMode = EditGame;
7666             ModeHighlight();
7667         }
7668         currentMove = forwardMostMove-1;
7669         DisplayMove(currentMove-1); /* before DisplayMoveError */
7670         SwitchClocks(forwardMostMove-1); // [HGM] race
7671         DisplayBothClocks();
7672         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7673                 parseList[currentMove], cps->which);
7674         DisplayMoveError(buf1);
7675         DrawPosition(FALSE, boards[currentMove]);
7676
7677         /* [HGM] illegal-move claim should forfeit game when Xboard */
7678         /* only passes fully legal moves                            */
7679         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7680             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7681                                 "False illegal-move claim", GE_XBOARD );
7682         }
7683         return;
7684     }
7685     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7686         /* Program has a broken "time" command that
7687            outputs a string not ending in newline.
7688            Don't use it. */
7689         cps->sendTime = 0;
7690     }
7691
7692     /*
7693      * If chess program startup fails, exit with an error message.
7694      * Attempts to recover here are futile.
7695      */
7696     if ((StrStr(message, "unknown host") != NULL)
7697         || (StrStr(message, "No remote directory") != NULL)
7698         || (StrStr(message, "not found") != NULL)
7699         || (StrStr(message, "No such file") != NULL)
7700         || (StrStr(message, "can't alloc") != NULL)
7701         || (StrStr(message, "Permission denied") != NULL)) {
7702
7703         cps->maybeThinking = FALSE;
7704         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7705                 cps->which, cps->program, cps->host, message);
7706         RemoveInputSource(cps->isr);
7707         DisplayFatalError(buf1, 0, 1);
7708         return;
7709     }
7710
7711     /*
7712      * Look for hint output
7713      */
7714     if (sscanf(message, "Hint: %s", buf1) == 1) {
7715         if (cps == &first && hintRequested) {
7716             hintRequested = FALSE;
7717             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7718                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7719                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7720                                     PosFlags(forwardMostMove),
7721                                     fromY, fromX, toY, toX, promoChar, buf1);
7722                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7723                 DisplayInformation(buf2);
7724             } else {
7725                 /* Hint move could not be parsed!? */
7726               snprintf(buf2, sizeof(buf2),
7727                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7728                         buf1, cps->which);
7729                 DisplayError(buf2, 0);
7730             }
7731         } else {
7732           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7733         }
7734         return;
7735     }
7736
7737     /*
7738      * Ignore other messages if game is not in progress
7739      */
7740     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7741         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7742
7743     /*
7744      * look for win, lose, draw, or draw offer
7745      */
7746     if (strncmp(message, "1-0", 3) == 0) {
7747         char *p, *q, *r = "";
7748         p = strchr(message, '{');
7749         if (p) {
7750             q = strchr(p, '}');
7751             if (q) {
7752                 *q = NULLCHAR;
7753                 r = p + 1;
7754             }
7755         }
7756         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7757         return;
7758     } else if (strncmp(message, "0-1", 3) == 0) {
7759         char *p, *q, *r = "";
7760         p = strchr(message, '{');
7761         if (p) {
7762             q = strchr(p, '}');
7763             if (q) {
7764                 *q = NULLCHAR;
7765                 r = p + 1;
7766             }
7767         }
7768         /* Kludge for Arasan 4.1 bug */
7769         if (strcmp(r, "Black resigns") == 0) {
7770             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7771             return;
7772         }
7773         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7774         return;
7775     } else if (strncmp(message, "1/2", 3) == 0) {
7776         char *p, *q, *r = "";
7777         p = strchr(message, '{');
7778         if (p) {
7779             q = strchr(p, '}');
7780             if (q) {
7781                 *q = NULLCHAR;
7782                 r = p + 1;
7783             }
7784         }
7785
7786         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7787         return;
7788
7789     } else if (strncmp(message, "White resign", 12) == 0) {
7790         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7791         return;
7792     } else if (strncmp(message, "Black resign", 12) == 0) {
7793         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7794         return;
7795     } else if (strncmp(message, "White matches", 13) == 0 ||
7796                strncmp(message, "Black matches", 13) == 0   ) {
7797         /* [HGM] ignore GNUShogi noises */
7798         return;
7799     } else if (strncmp(message, "White", 5) == 0 &&
7800                message[5] != '(' &&
7801                StrStr(message, "Black") == NULL) {
7802         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7803         return;
7804     } else if (strncmp(message, "Black", 5) == 0 &&
7805                message[5] != '(') {
7806         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7807         return;
7808     } else if (strcmp(message, "resign") == 0 ||
7809                strcmp(message, "computer resigns") == 0) {
7810         switch (gameMode) {
7811           case MachinePlaysBlack:
7812           case IcsPlayingBlack:
7813             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7814             break;
7815           case MachinePlaysWhite:
7816           case IcsPlayingWhite:
7817             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7818             break;
7819           case TwoMachinesPlay:
7820             if (cps->twoMachinesColor[0] == 'w')
7821               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7822             else
7823               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7824             break;
7825           default:
7826             /* can't happen */
7827             break;
7828         }
7829         return;
7830     } else if (strncmp(message, "opponent mates", 14) == 0) {
7831         switch (gameMode) {
7832           case MachinePlaysBlack:
7833           case IcsPlayingBlack:
7834             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7835             break;
7836           case MachinePlaysWhite:
7837           case IcsPlayingWhite:
7838             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7839             break;
7840           case TwoMachinesPlay:
7841             if (cps->twoMachinesColor[0] == 'w')
7842               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7843             else
7844               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7845             break;
7846           default:
7847             /* can't happen */
7848             break;
7849         }
7850         return;
7851     } else if (strncmp(message, "computer mates", 14) == 0) {
7852         switch (gameMode) {
7853           case MachinePlaysBlack:
7854           case IcsPlayingBlack:
7855             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7856             break;
7857           case MachinePlaysWhite:
7858           case IcsPlayingWhite:
7859             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7860             break;
7861           case TwoMachinesPlay:
7862             if (cps->twoMachinesColor[0] == 'w')
7863               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7864             else
7865               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7866             break;
7867           default:
7868             /* can't happen */
7869             break;
7870         }
7871         return;
7872     } else if (strncmp(message, "checkmate", 9) == 0) {
7873         if (WhiteOnMove(forwardMostMove)) {
7874             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7875         } else {
7876             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7877         }
7878         return;
7879     } else if (strstr(message, "Draw") != NULL ||
7880                strstr(message, "game is a draw") != NULL) {
7881         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7882         return;
7883     } else if (strstr(message, "offer") != NULL &&
7884                strstr(message, "draw") != NULL) {
7885 #if ZIPPY
7886         if (appData.zippyPlay && first.initDone) {
7887             /* Relay offer to ICS */
7888             SendToICS(ics_prefix);
7889             SendToICS("draw\n");
7890         }
7891 #endif
7892         cps->offeredDraw = 2; /* valid until this engine moves twice */
7893         if (gameMode == TwoMachinesPlay) {
7894             if (cps->other->offeredDraw) {
7895                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7896             /* [HGM] in two-machine mode we delay relaying draw offer      */
7897             /* until after we also have move, to see if it is really claim */
7898             }
7899         } else if (gameMode == MachinePlaysWhite ||
7900                    gameMode == MachinePlaysBlack) {
7901           if (userOfferedDraw) {
7902             DisplayInformation(_("Machine accepts your draw offer"));
7903             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7904           } else {
7905             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7906           }
7907         }
7908     }
7909
7910
7911     /*
7912      * Look for thinking output
7913      */
7914     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7915           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7916                                 ) {
7917         int plylev, mvleft, mvtot, curscore, time;
7918         char mvname[MOVE_LEN];
7919         u64 nodes; // [DM]
7920         char plyext;
7921         int ignore = FALSE;
7922         int prefixHint = FALSE;
7923         mvname[0] = NULLCHAR;
7924
7925         switch (gameMode) {
7926           case MachinePlaysBlack:
7927           case IcsPlayingBlack:
7928             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7929             break;
7930           case MachinePlaysWhite:
7931           case IcsPlayingWhite:
7932             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7933             break;
7934           case AnalyzeMode:
7935           case AnalyzeFile:
7936             break;
7937           case IcsObserving: /* [DM] icsEngineAnalyze */
7938             if (!appData.icsEngineAnalyze) ignore = TRUE;
7939             break;
7940           case TwoMachinesPlay:
7941             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7942                 ignore = TRUE;
7943             }
7944             break;
7945           default:
7946             ignore = TRUE;
7947             break;
7948         }
7949
7950         if (!ignore) {
7951             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7952             buf1[0] = NULLCHAR;
7953             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7954                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7955
7956                 if (plyext != ' ' && plyext != '\t') {
7957                     time *= 100;
7958                 }
7959
7960                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7961                 if( cps->scoreIsAbsolute &&
7962                     ( gameMode == MachinePlaysBlack ||
7963                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7964                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7965                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7966                      !WhiteOnMove(currentMove)
7967                     ) )
7968                 {
7969                     curscore = -curscore;
7970                 }
7971
7972
7973                 tempStats.depth = plylev;
7974                 tempStats.nodes = nodes;
7975                 tempStats.time = time;
7976                 tempStats.score = curscore;
7977                 tempStats.got_only_move = 0;
7978
7979                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7980                         int ticklen;
7981
7982                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7983                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7984                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7985                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7986                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7987                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7988                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7989                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7990                 }
7991
7992                 /* Buffer overflow protection */
7993                 if (buf1[0] != NULLCHAR) {
7994                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7995                         && appData.debugMode) {
7996                         fprintf(debugFP,
7997                                 "PV is too long; using the first %u bytes.\n",
7998                                 (unsigned) sizeof(tempStats.movelist) - 1);
7999                     }
8000
8001                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8002                 } else {
8003                     sprintf(tempStats.movelist, " no PV\n");
8004                 }
8005
8006                 if (tempStats.seen_stat) {
8007                     tempStats.ok_to_send = 1;
8008                 }
8009
8010                 if (strchr(tempStats.movelist, '(') != NULL) {
8011                     tempStats.line_is_book = 1;
8012                     tempStats.nr_moves = 0;
8013                     tempStats.moves_left = 0;
8014                 } else {
8015                     tempStats.line_is_book = 0;
8016                 }
8017
8018                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8019                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8020
8021                 SendProgramStatsToFrontend( cps, &tempStats );
8022
8023                 /*
8024                     [AS] Protect the thinkOutput buffer from overflow... this
8025                     is only useful if buf1 hasn't overflowed first!
8026                 */
8027                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8028                          plylev,
8029                          (gameMode == TwoMachinesPlay ?
8030                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8031                          ((double) curscore) / 100.0,
8032                          prefixHint ? lastHint : "",
8033                          prefixHint ? " " : "" );
8034
8035                 if( buf1[0] != NULLCHAR ) {
8036                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8037
8038                     if( strlen(buf1) > max_len ) {
8039                         if( appData.debugMode) {
8040                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8041                         }
8042                         buf1[max_len+1] = '\0';
8043                     }
8044
8045                     strcat( thinkOutput, buf1 );
8046                 }
8047
8048                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8049                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8050                     DisplayMove(currentMove - 1);
8051                 }
8052                 return;
8053
8054             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8055                 /* crafty (9.25+) says "(only move) <move>"
8056                  * if there is only 1 legal move
8057                  */
8058                 sscanf(p, "(only move) %s", buf1);
8059                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8060                 sprintf(programStats.movelist, "%s (only move)", buf1);
8061                 programStats.depth = 1;
8062                 programStats.nr_moves = 1;
8063                 programStats.moves_left = 1;
8064                 programStats.nodes = 1;
8065                 programStats.time = 1;
8066                 programStats.got_only_move = 1;
8067
8068                 /* Not really, but we also use this member to
8069                    mean "line isn't going to change" (Crafty
8070                    isn't searching, so stats won't change) */
8071                 programStats.line_is_book = 1;
8072
8073                 SendProgramStatsToFrontend( cps, &programStats );
8074
8075                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8076                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8077                     DisplayMove(currentMove - 1);
8078                 }
8079                 return;
8080             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8081                               &time, &nodes, &plylev, &mvleft,
8082                               &mvtot, mvname) >= 5) {
8083                 /* The stat01: line is from Crafty (9.29+) in response
8084                    to the "." command */
8085                 programStats.seen_stat = 1;
8086                 cps->maybeThinking = TRUE;
8087
8088                 if (programStats.got_only_move || !appData.periodicUpdates)
8089                   return;
8090
8091                 programStats.depth = plylev;
8092                 programStats.time = time;
8093                 programStats.nodes = nodes;
8094                 programStats.moves_left = mvleft;
8095                 programStats.nr_moves = mvtot;
8096                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8097                 programStats.ok_to_send = 1;
8098                 programStats.movelist[0] = '\0';
8099
8100                 SendProgramStatsToFrontend( cps, &programStats );
8101
8102                 return;
8103
8104             } else if (strncmp(message,"++",2) == 0) {
8105                 /* Crafty 9.29+ outputs this */
8106                 programStats.got_fail = 2;
8107                 return;
8108
8109             } else if (strncmp(message,"--",2) == 0) {
8110                 /* Crafty 9.29+ outputs this */
8111                 programStats.got_fail = 1;
8112                 return;
8113
8114             } else if (thinkOutput[0] != NULLCHAR &&
8115                        strncmp(message, "    ", 4) == 0) {
8116                 unsigned message_len;
8117
8118                 p = message;
8119                 while (*p && *p == ' ') p++;
8120
8121                 message_len = strlen( p );
8122
8123                 /* [AS] Avoid buffer overflow */
8124                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8125                     strcat(thinkOutput, " ");
8126                     strcat(thinkOutput, p);
8127                 }
8128
8129                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8130                     strcat(programStats.movelist, " ");
8131                     strcat(programStats.movelist, p);
8132                 }
8133
8134                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8135                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8136                     DisplayMove(currentMove - 1);
8137                 }
8138                 return;
8139             }
8140         }
8141         else {
8142             buf1[0] = NULLCHAR;
8143
8144             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8145                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8146             {
8147                 ChessProgramStats cpstats;
8148
8149                 if (plyext != ' ' && plyext != '\t') {
8150                     time *= 100;
8151                 }
8152
8153                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8154                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8155                     curscore = -curscore;
8156                 }
8157
8158                 cpstats.depth = plylev;
8159                 cpstats.nodes = nodes;
8160                 cpstats.time = time;
8161                 cpstats.score = curscore;
8162                 cpstats.got_only_move = 0;
8163                 cpstats.movelist[0] = '\0';
8164
8165                 if (buf1[0] != NULLCHAR) {
8166                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8167                 }
8168
8169                 cpstats.ok_to_send = 0;
8170                 cpstats.line_is_book = 0;
8171                 cpstats.nr_moves = 0;
8172                 cpstats.moves_left = 0;
8173
8174                 SendProgramStatsToFrontend( cps, &cpstats );
8175             }
8176         }
8177     }
8178 }
8179
8180
8181 /* Parse a game score from the character string "game", and
8182    record it as the history of the current game.  The game
8183    score is NOT assumed to start from the standard position.
8184    The display is not updated in any way.
8185    */
8186 void
8187 ParseGameHistory(game)
8188      char *game;
8189 {
8190     ChessMove moveType;
8191     int fromX, fromY, toX, toY, boardIndex;
8192     char promoChar;
8193     char *p, *q;
8194     char buf[MSG_SIZ];
8195
8196     if (appData.debugMode)
8197       fprintf(debugFP, "Parsing game history: %s\n", game);
8198
8199     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8200     gameInfo.site = StrSave(appData.icsHost);
8201     gameInfo.date = PGNDate();
8202     gameInfo.round = StrSave("-");
8203
8204     /* Parse out names of players */
8205     while (*game == ' ') game++;
8206     p = buf;
8207     while (*game != ' ') *p++ = *game++;
8208     *p = NULLCHAR;
8209     gameInfo.white = StrSave(buf);
8210     while (*game == ' ') game++;
8211     p = buf;
8212     while (*game != ' ' && *game != '\n') *p++ = *game++;
8213     *p = NULLCHAR;
8214     gameInfo.black = StrSave(buf);
8215
8216     /* Parse moves */
8217     boardIndex = blackPlaysFirst ? 1 : 0;
8218     yynewstr(game);
8219     for (;;) {
8220         yyboardindex = boardIndex;
8221         moveType = (ChessMove) Myylex();
8222         switch (moveType) {
8223           case IllegalMove:             /* maybe suicide chess, etc. */
8224   if (appData.debugMode) {
8225     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8226     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8227     setbuf(debugFP, NULL);
8228   }
8229           case WhitePromotion:
8230           case BlackPromotion:
8231           case WhiteNonPromotion:
8232           case BlackNonPromotion:
8233           case NormalMove:
8234           case WhiteCapturesEnPassant:
8235           case BlackCapturesEnPassant:
8236           case WhiteKingSideCastle:
8237           case WhiteQueenSideCastle:
8238           case BlackKingSideCastle:
8239           case BlackQueenSideCastle:
8240           case WhiteKingSideCastleWild:
8241           case WhiteQueenSideCastleWild:
8242           case BlackKingSideCastleWild:
8243           case BlackQueenSideCastleWild:
8244           /* PUSH Fabien */
8245           case WhiteHSideCastleFR:
8246           case WhiteASideCastleFR:
8247           case BlackHSideCastleFR:
8248           case BlackASideCastleFR:
8249           /* POP Fabien */
8250             fromX = currentMoveString[0] - AAA;
8251             fromY = currentMoveString[1] - ONE;
8252             toX = currentMoveString[2] - AAA;
8253             toY = currentMoveString[3] - ONE;
8254             promoChar = currentMoveString[4];
8255             break;
8256           case WhiteDrop:
8257           case BlackDrop:
8258             fromX = moveType == WhiteDrop ?
8259               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8260             (int) CharToPiece(ToLower(currentMoveString[0]));
8261             fromY = DROP_RANK;
8262             toX = currentMoveString[2] - AAA;
8263             toY = currentMoveString[3] - ONE;
8264             promoChar = NULLCHAR;
8265             break;
8266           case AmbiguousMove:
8267             /* bug? */
8268             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8269   if (appData.debugMode) {
8270     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8271     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8272     setbuf(debugFP, NULL);
8273   }
8274             DisplayError(buf, 0);
8275             return;
8276           case ImpossibleMove:
8277             /* bug? */
8278             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8279   if (appData.debugMode) {
8280     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8281     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8282     setbuf(debugFP, NULL);
8283   }
8284             DisplayError(buf, 0);
8285             return;
8286           case EndOfFile:
8287             if (boardIndex < backwardMostMove) {
8288                 /* Oops, gap.  How did that happen? */
8289                 DisplayError(_("Gap in move list"), 0);
8290                 return;
8291             }
8292             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8293             if (boardIndex > forwardMostMove) {
8294                 forwardMostMove = boardIndex;
8295             }
8296             return;
8297           case ElapsedTime:
8298             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8299                 strcat(parseList[boardIndex-1], " ");
8300                 strcat(parseList[boardIndex-1], yy_text);
8301             }
8302             continue;
8303           case Comment:
8304           case PGNTag:
8305           case NAG:
8306           default:
8307             /* ignore */
8308             continue;
8309           case WhiteWins:
8310           case BlackWins:
8311           case GameIsDrawn:
8312           case GameUnfinished:
8313             if (gameMode == IcsExamining) {
8314                 if (boardIndex < backwardMostMove) {
8315                     /* Oops, gap.  How did that happen? */
8316                     return;
8317                 }
8318                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8319                 return;
8320             }
8321             gameInfo.result = moveType;
8322             p = strchr(yy_text, '{');
8323             if (p == NULL) p = strchr(yy_text, '(');
8324             if (p == NULL) {
8325                 p = yy_text;
8326                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8327             } else {
8328                 q = strchr(p, *p == '{' ? '}' : ')');
8329                 if (q != NULL) *q = NULLCHAR;
8330                 p++;
8331             }
8332             gameInfo.resultDetails = StrSave(p);
8333             continue;
8334         }
8335         if (boardIndex >= forwardMostMove &&
8336             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8337             backwardMostMove = blackPlaysFirst ? 1 : 0;
8338             return;
8339         }
8340         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8341                                  fromY, fromX, toY, toX, promoChar,
8342                                  parseList[boardIndex]);
8343         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8344         /* currentMoveString is set as a side-effect of yylex */
8345         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8346         strcat(moveList[boardIndex], "\n");
8347         boardIndex++;
8348         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8349         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8350           case MT_NONE:
8351           case MT_STALEMATE:
8352           default:
8353             break;
8354           case MT_CHECK:
8355             if(gameInfo.variant != VariantShogi)
8356                 strcat(parseList[boardIndex - 1], "+");
8357             break;
8358           case MT_CHECKMATE:
8359           case MT_STAINMATE:
8360             strcat(parseList[boardIndex - 1], "#");
8361             break;
8362         }
8363     }
8364 }
8365
8366
8367 /* Apply a move to the given board  */
8368 void
8369 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8370      int fromX, fromY, toX, toY;
8371      int promoChar;
8372      Board board;
8373 {
8374   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8375   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8376
8377     /* [HGM] compute & store e.p. status and castling rights for new position */
8378     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8379
8380       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8381       oldEP = (signed char)board[EP_STATUS];
8382       board[EP_STATUS] = EP_NONE;
8383
8384       if( board[toY][toX] != EmptySquare )
8385            board[EP_STATUS] = EP_CAPTURE;
8386
8387   if (fromY == DROP_RANK) {
8388         /* must be first */
8389         piece = board[toY][toX] = (ChessSquare) fromX;
8390   } else {
8391       int i;
8392
8393       if( board[fromY][fromX] == WhitePawn ) {
8394            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8395                board[EP_STATUS] = EP_PAWN_MOVE;
8396            if( toY-fromY==2) {
8397                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8398                         gameInfo.variant != VariantBerolina || toX < fromX)
8399                       board[EP_STATUS] = toX | berolina;
8400                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8401                         gameInfo.variant != VariantBerolina || toX > fromX)
8402                       board[EP_STATUS] = toX;
8403            }
8404       } else
8405       if( board[fromY][fromX] == BlackPawn ) {
8406            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8407                board[EP_STATUS] = EP_PAWN_MOVE;
8408            if( toY-fromY== -2) {
8409                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8410                         gameInfo.variant != VariantBerolina || toX < fromX)
8411                       board[EP_STATUS] = toX | berolina;
8412                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8413                         gameInfo.variant != VariantBerolina || toX > fromX)
8414                       board[EP_STATUS] = toX;
8415            }
8416        }
8417
8418        for(i=0; i<nrCastlingRights; i++) {
8419            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8420               board[CASTLING][i] == toX   && castlingRank[i] == toY
8421              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8422        }
8423
8424      if (fromX == toX && fromY == toY) return;
8425
8426      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8427      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8428      if(gameInfo.variant == VariantKnightmate)
8429          king += (int) WhiteUnicorn - (int) WhiteKing;
8430
8431     /* Code added by Tord: */
8432     /* FRC castling assumed when king captures friendly rook. */
8433     if (board[fromY][fromX] == WhiteKing &&
8434              board[toY][toX] == WhiteRook) {
8435       board[fromY][fromX] = EmptySquare;
8436       board[toY][toX] = EmptySquare;
8437       if(toX > fromX) {
8438         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8439       } else {
8440         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8441       }
8442     } else if (board[fromY][fromX] == BlackKing &&
8443                board[toY][toX] == BlackRook) {
8444       board[fromY][fromX] = EmptySquare;
8445       board[toY][toX] = EmptySquare;
8446       if(toX > fromX) {
8447         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8448       } else {
8449         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8450       }
8451     /* End of code added by Tord */
8452
8453     } else if (board[fromY][fromX] == king
8454         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8455         && toY == fromY && toX > fromX+1) {
8456         board[fromY][fromX] = EmptySquare;
8457         board[toY][toX] = king;
8458         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8459         board[fromY][BOARD_RGHT-1] = EmptySquare;
8460     } else if (board[fromY][fromX] == king
8461         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8462                && toY == fromY && toX < fromX-1) {
8463         board[fromY][fromX] = EmptySquare;
8464         board[toY][toX] = king;
8465         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8466         board[fromY][BOARD_LEFT] = EmptySquare;
8467     } else if (board[fromY][fromX] == WhitePawn
8468                && toY >= BOARD_HEIGHT-promoRank
8469                && gameInfo.variant != VariantXiangqi
8470                ) {
8471         /* white pawn promotion */
8472         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8473         if (board[toY][toX] == EmptySquare) {
8474             board[toY][toX] = WhiteQueen;
8475         }
8476         if(gameInfo.variant==VariantBughouse ||
8477            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8478             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8479         board[fromY][fromX] = EmptySquare;
8480     } else if ((fromY == BOARD_HEIGHT-4)
8481                && (toX != fromX)
8482                && gameInfo.variant != VariantXiangqi
8483                && gameInfo.variant != VariantBerolina
8484                && (board[fromY][fromX] == WhitePawn)
8485                && (board[toY][toX] == EmptySquare)) {
8486         board[fromY][fromX] = EmptySquare;
8487         board[toY][toX] = WhitePawn;
8488         captured = board[toY - 1][toX];
8489         board[toY - 1][toX] = EmptySquare;
8490     } else if ((fromY == BOARD_HEIGHT-4)
8491                && (toX == fromX)
8492                && gameInfo.variant == VariantBerolina
8493                && (board[fromY][fromX] == WhitePawn)
8494                && (board[toY][toX] == EmptySquare)) {
8495         board[fromY][fromX] = EmptySquare;
8496         board[toY][toX] = WhitePawn;
8497         if(oldEP & EP_BEROLIN_A) {
8498                 captured = board[fromY][fromX-1];
8499                 board[fromY][fromX-1] = EmptySquare;
8500         }else{  captured = board[fromY][fromX+1];
8501                 board[fromY][fromX+1] = EmptySquare;
8502         }
8503     } else if (board[fromY][fromX] == king
8504         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8505                && toY == fromY && toX > fromX+1) {
8506         board[fromY][fromX] = EmptySquare;
8507         board[toY][toX] = king;
8508         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8509         board[fromY][BOARD_RGHT-1] = EmptySquare;
8510     } else if (board[fromY][fromX] == king
8511         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8512                && toY == fromY && toX < fromX-1) {
8513         board[fromY][fromX] = EmptySquare;
8514         board[toY][toX] = king;
8515         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8516         board[fromY][BOARD_LEFT] = EmptySquare;
8517     } else if (fromY == 7 && fromX == 3
8518                && board[fromY][fromX] == BlackKing
8519                && toY == 7 && toX == 5) {
8520         board[fromY][fromX] = EmptySquare;
8521         board[toY][toX] = BlackKing;
8522         board[fromY][7] = EmptySquare;
8523         board[toY][4] = BlackRook;
8524     } else if (fromY == 7 && fromX == 3
8525                && board[fromY][fromX] == BlackKing
8526                && toY == 7 && toX == 1) {
8527         board[fromY][fromX] = EmptySquare;
8528         board[toY][toX] = BlackKing;
8529         board[fromY][0] = EmptySquare;
8530         board[toY][2] = BlackRook;
8531     } else if (board[fromY][fromX] == BlackPawn
8532                && toY < promoRank
8533                && gameInfo.variant != VariantXiangqi
8534                ) {
8535         /* black pawn promotion */
8536         board[toY][toX] = CharToPiece(ToLower(promoChar));
8537         if (board[toY][toX] == EmptySquare) {
8538             board[toY][toX] = BlackQueen;
8539         }
8540         if(gameInfo.variant==VariantBughouse ||
8541            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8542             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8543         board[fromY][fromX] = EmptySquare;
8544     } else if ((fromY == 3)
8545                && (toX != fromX)
8546                && gameInfo.variant != VariantXiangqi
8547                && gameInfo.variant != VariantBerolina
8548                && (board[fromY][fromX] == BlackPawn)
8549                && (board[toY][toX] == EmptySquare)) {
8550         board[fromY][fromX] = EmptySquare;
8551         board[toY][toX] = BlackPawn;
8552         captured = board[toY + 1][toX];
8553         board[toY + 1][toX] = EmptySquare;
8554     } else if ((fromY == 3)
8555                && (toX == fromX)
8556                && gameInfo.variant == VariantBerolina
8557                && (board[fromY][fromX] == BlackPawn)
8558                && (board[toY][toX] == EmptySquare)) {
8559         board[fromY][fromX] = EmptySquare;
8560         board[toY][toX] = BlackPawn;
8561         if(oldEP & EP_BEROLIN_A) {
8562                 captured = board[fromY][fromX-1];
8563                 board[fromY][fromX-1] = EmptySquare;
8564         }else{  captured = board[fromY][fromX+1];
8565                 board[fromY][fromX+1] = EmptySquare;
8566         }
8567     } else {
8568         board[toY][toX] = board[fromY][fromX];
8569         board[fromY][fromX] = EmptySquare;
8570     }
8571   }
8572
8573     if (gameInfo.holdingsWidth != 0) {
8574
8575       /* !!A lot more code needs to be written to support holdings  */
8576       /* [HGM] OK, so I have written it. Holdings are stored in the */
8577       /* penultimate board files, so they are automaticlly stored   */
8578       /* in the game history.                                       */
8579       if (fromY == DROP_RANK) {
8580         /* Delete from holdings, by decreasing count */
8581         /* and erasing image if necessary            */
8582         p = (int) fromX;
8583         if(p < (int) BlackPawn) { /* white drop */
8584              p -= (int)WhitePawn;
8585                  p = PieceToNumber((ChessSquare)p);
8586              if(p >= gameInfo.holdingsSize) p = 0;
8587              if(--board[p][BOARD_WIDTH-2] <= 0)
8588                   board[p][BOARD_WIDTH-1] = EmptySquare;
8589              if((int)board[p][BOARD_WIDTH-2] < 0)
8590                         board[p][BOARD_WIDTH-2] = 0;
8591         } else {                  /* black drop */
8592              p -= (int)BlackPawn;
8593                  p = PieceToNumber((ChessSquare)p);
8594              if(p >= gameInfo.holdingsSize) p = 0;
8595              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8596                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8597              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8598                         board[BOARD_HEIGHT-1-p][1] = 0;
8599         }
8600       }
8601       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8602           && gameInfo.variant != VariantBughouse        ) {
8603         /* [HGM] holdings: Add to holdings, if holdings exist */
8604         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8605                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8606                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8607         }
8608         p = (int) captured;
8609         if (p >= (int) BlackPawn) {
8610           p -= (int)BlackPawn;
8611           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8612                   /* in Shogi restore piece to its original  first */
8613                   captured = (ChessSquare) (DEMOTED captured);
8614                   p = DEMOTED p;
8615           }
8616           p = PieceToNumber((ChessSquare)p);
8617           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8618           board[p][BOARD_WIDTH-2]++;
8619           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8620         } else {
8621           p -= (int)WhitePawn;
8622           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8623                   captured = (ChessSquare) (DEMOTED captured);
8624                   p = DEMOTED p;
8625           }
8626           p = PieceToNumber((ChessSquare)p);
8627           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8628           board[BOARD_HEIGHT-1-p][1]++;
8629           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8630         }
8631       }
8632     } else if (gameInfo.variant == VariantAtomic) {
8633       if (captured != EmptySquare) {
8634         int y, x;
8635         for (y = toY-1; y <= toY+1; y++) {
8636           for (x = toX-1; x <= toX+1; x++) {
8637             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8638                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8639               board[y][x] = EmptySquare;
8640             }
8641           }
8642         }
8643         board[toY][toX] = EmptySquare;
8644       }
8645     }
8646     if(promoChar == '+') {
8647         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8648         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8649     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8650         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8651     }
8652     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8653                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8654         // [HGM] superchess: take promotion piece out of holdings
8655         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8656         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8657             if(!--board[k][BOARD_WIDTH-2])
8658                 board[k][BOARD_WIDTH-1] = EmptySquare;
8659         } else {
8660             if(!--board[BOARD_HEIGHT-1-k][1])
8661                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8662         }
8663     }
8664
8665 }
8666
8667 /* Updates forwardMostMove */
8668 void
8669 MakeMove(fromX, fromY, toX, toY, promoChar)
8670      int fromX, fromY, toX, toY;
8671      int promoChar;
8672 {
8673 //    forwardMostMove++; // [HGM] bare: moved downstream
8674
8675     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8676         int timeLeft; static int lastLoadFlag=0; int king, piece;
8677         piece = boards[forwardMostMove][fromY][fromX];
8678         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8679         if(gameInfo.variant == VariantKnightmate)
8680             king += (int) WhiteUnicorn - (int) WhiteKing;
8681         if(forwardMostMove == 0) {
8682             if(blackPlaysFirst)
8683                 fprintf(serverMoves, "%s;", second.tidy);
8684             fprintf(serverMoves, "%s;", first.tidy);
8685             if(!blackPlaysFirst)
8686                 fprintf(serverMoves, "%s;", second.tidy);
8687         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8688         lastLoadFlag = loadFlag;
8689         // print base move
8690         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8691         // print castling suffix
8692         if( toY == fromY && piece == king ) {
8693             if(toX-fromX > 1)
8694                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8695             if(fromX-toX >1)
8696                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8697         }
8698         // e.p. suffix
8699         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8700              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8701              boards[forwardMostMove][toY][toX] == EmptySquare
8702              && fromX != toX && fromY != toY)
8703                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8704         // promotion suffix
8705         if(promoChar != NULLCHAR)
8706                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8707         if(!loadFlag) {
8708             fprintf(serverMoves, "/%d/%d",
8709                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8710             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8711             else                      timeLeft = blackTimeRemaining/1000;
8712             fprintf(serverMoves, "/%d", timeLeft);
8713         }
8714         fflush(serverMoves);
8715     }
8716
8717     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8718       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8719                         0, 1);
8720       return;
8721     }
8722     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8723     if (commentList[forwardMostMove+1] != NULL) {
8724         free(commentList[forwardMostMove+1]);
8725         commentList[forwardMostMove+1] = NULL;
8726     }
8727     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8728     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8729     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8730     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8731     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8732     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8733     gameInfo.result = GameUnfinished;
8734     if (gameInfo.resultDetails != NULL) {
8735         free(gameInfo.resultDetails);
8736         gameInfo.resultDetails = NULL;
8737     }
8738     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8739                               moveList[forwardMostMove - 1]);
8740     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8741                              PosFlags(forwardMostMove - 1),
8742                              fromY, fromX, toY, toX, promoChar,
8743                              parseList[forwardMostMove - 1]);
8744     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8745       case MT_NONE:
8746       case MT_STALEMATE:
8747       default:
8748         break;
8749       case MT_CHECK:
8750         if(gameInfo.variant != VariantShogi)
8751             strcat(parseList[forwardMostMove - 1], "+");
8752         break;
8753       case MT_CHECKMATE:
8754       case MT_STAINMATE:
8755         strcat(parseList[forwardMostMove - 1], "#");
8756         break;
8757     }
8758     if (appData.debugMode) {
8759         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8760     }
8761
8762 }
8763
8764 /* Updates currentMove if not pausing */
8765 void
8766 ShowMove(fromX, fromY, toX, toY)
8767 {
8768     int instant = (gameMode == PlayFromGameFile) ?
8769         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8770     if(appData.noGUI) return;
8771     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8772         if (!instant) {
8773             if (forwardMostMove == currentMove + 1) {
8774                 AnimateMove(boards[forwardMostMove - 1],
8775                             fromX, fromY, toX, toY);
8776             }
8777             if (appData.highlightLastMove) {
8778                 SetHighlights(fromX, fromY, toX, toY);
8779             }
8780         }
8781         currentMove = forwardMostMove;
8782     }
8783
8784     if (instant) return;
8785
8786     DisplayMove(currentMove - 1);
8787     DrawPosition(FALSE, boards[currentMove]);
8788     DisplayBothClocks();
8789     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8790 }
8791
8792 void SendEgtPath(ChessProgramState *cps)
8793 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8794         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8795
8796         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8797
8798         while(*p) {
8799             char c, *q = name+1, *r, *s;
8800
8801             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8802             while(*p && *p != ',') *q++ = *p++;
8803             *q++ = ':'; *q = 0;
8804             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8805                 strcmp(name, ",nalimov:") == 0 ) {
8806                 // take nalimov path from the menu-changeable option first, if it is defined
8807               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8808                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8809             } else
8810             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8811                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8812                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8813                 s = r = StrStr(s, ":") + 1; // beginning of path info
8814                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8815                 c = *r; *r = 0;             // temporarily null-terminate path info
8816                     *--q = 0;               // strip of trailig ':' from name
8817                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8818                 *r = c;
8819                 SendToProgram(buf,cps);     // send egtbpath command for this format
8820             }
8821             if(*p == ',') p++; // read away comma to position for next format name
8822         }
8823 }
8824
8825 void
8826 InitChessProgram(cps, setup)
8827      ChessProgramState *cps;
8828      int setup; /* [HGM] needed to setup FRC opening position */
8829 {
8830     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8831     if (appData.noChessProgram) return;
8832     hintRequested = FALSE;
8833     bookRequested = FALSE;
8834
8835     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8836     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8837     if(cps->memSize) { /* [HGM] memory */
8838       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8839         SendToProgram(buf, cps);
8840     }
8841     SendEgtPath(cps); /* [HGM] EGT */
8842     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8843       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8844         SendToProgram(buf, cps);
8845     }
8846
8847     SendToProgram(cps->initString, cps);
8848     if (gameInfo.variant != VariantNormal &&
8849         gameInfo.variant != VariantLoadable
8850         /* [HGM] also send variant if board size non-standard */
8851         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8852                                             ) {
8853       char *v = VariantName(gameInfo.variant);
8854       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8855         /* [HGM] in protocol 1 we have to assume all variants valid */
8856         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8857         DisplayFatalError(buf, 0, 1);
8858         return;
8859       }
8860
8861       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8862       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8863       if( gameInfo.variant == VariantXiangqi )
8864            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8865       if( gameInfo.variant == VariantShogi )
8866            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8867       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8868            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8869       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8870                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8871            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8872       if( gameInfo.variant == VariantCourier )
8873            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8874       if( gameInfo.variant == VariantSuper )
8875            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8876       if( gameInfo.variant == VariantGreat )
8877            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8878
8879       if(overruled) {
8880         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8881                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8882            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8883            if(StrStr(cps->variants, b) == NULL) {
8884                // specific sized variant not known, check if general sizing allowed
8885                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8886                    if(StrStr(cps->variants, "boardsize") == NULL) {
8887                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8888                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8889                        DisplayFatalError(buf, 0, 1);
8890                        return;
8891                    }
8892                    /* [HGM] here we really should compare with the maximum supported board size */
8893                }
8894            }
8895       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8896       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8897       SendToProgram(buf, cps);
8898     }
8899     currentlyInitializedVariant = gameInfo.variant;
8900
8901     /* [HGM] send opening position in FRC to first engine */
8902     if(setup) {
8903           SendToProgram("force\n", cps);
8904           SendBoard(cps, 0);
8905           /* engine is now in force mode! Set flag to wake it up after first move. */
8906           setboardSpoiledMachineBlack = 1;
8907     }
8908
8909     if (cps->sendICS) {
8910       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8911       SendToProgram(buf, cps);
8912     }
8913     cps->maybeThinking = FALSE;
8914     cps->offeredDraw = 0;
8915     if (!appData.icsActive) {
8916         SendTimeControl(cps, movesPerSession, timeControl,
8917                         timeIncrement, appData.searchDepth,
8918                         searchTime);
8919     }
8920     if (appData.showThinking
8921         // [HGM] thinking: four options require thinking output to be sent
8922         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8923                                 ) {
8924         SendToProgram("post\n", cps);
8925     }
8926     SendToProgram("hard\n", cps);
8927     if (!appData.ponderNextMove) {
8928         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8929            it without being sure what state we are in first.  "hard"
8930            is not a toggle, so that one is OK.
8931          */
8932         SendToProgram("easy\n", cps);
8933     }
8934     if (cps->usePing) {
8935       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8936       SendToProgram(buf, cps);
8937     }
8938     cps->initDone = TRUE;
8939 }
8940
8941
8942 void
8943 StartChessProgram(cps)
8944      ChessProgramState *cps;
8945 {
8946     char buf[MSG_SIZ];
8947     int err;
8948
8949     if (appData.noChessProgram) return;
8950     cps->initDone = FALSE;
8951
8952     if (strcmp(cps->host, "localhost") == 0) {
8953         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8954     } else if (*appData.remoteShell == NULLCHAR) {
8955         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8956     } else {
8957         if (*appData.remoteUser == NULLCHAR) {
8958           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8959                     cps->program);
8960         } else {
8961           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8962                     cps->host, appData.remoteUser, cps->program);
8963         }
8964         err = StartChildProcess(buf, "", &cps->pr);
8965     }
8966
8967     if (err != 0) {
8968       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8969         DisplayFatalError(buf, err, 1);
8970         cps->pr = NoProc;
8971         cps->isr = NULL;
8972         return;
8973     }
8974
8975     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8976     if (cps->protocolVersion > 1) {
8977       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8978       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8979       cps->comboCnt = 0;  //                and values of combo boxes
8980       SendToProgram(buf, cps);
8981     } else {
8982       SendToProgram("xboard\n", cps);
8983     }
8984 }
8985
8986
8987 void
8988 TwoMachinesEventIfReady P((void))
8989 {
8990   if (first.lastPing != first.lastPong) {
8991     DisplayMessage("", _("Waiting for first chess program"));
8992     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8993     return;
8994   }
8995   if (second.lastPing != second.lastPong) {
8996     DisplayMessage("", _("Waiting for second chess program"));
8997     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8998     return;
8999   }
9000   ThawUI();
9001   TwoMachinesEvent();
9002 }
9003
9004 void
9005 NextMatchGame P((void))
9006 {
9007     int index; /* [HGM] autoinc: step load index during match */
9008     Reset(FALSE, TRUE);
9009     if (*appData.loadGameFile != NULLCHAR) {
9010         index = appData.loadGameIndex;
9011         if(index < 0) { // [HGM] autoinc
9012             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9013             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9014         }
9015         LoadGameFromFile(appData.loadGameFile,
9016                          index,
9017                          appData.loadGameFile, FALSE);
9018     } else if (*appData.loadPositionFile != NULLCHAR) {
9019         index = appData.loadPositionIndex;
9020         if(index < 0) { // [HGM] autoinc
9021             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9022             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9023         }
9024         LoadPositionFromFile(appData.loadPositionFile,
9025                              index,
9026                              appData.loadPositionFile);
9027     }
9028     TwoMachinesEventIfReady();
9029 }
9030
9031 void UserAdjudicationEvent( int result )
9032 {
9033     ChessMove gameResult = GameIsDrawn;
9034
9035     if( result > 0 ) {
9036         gameResult = WhiteWins;
9037     }
9038     else if( result < 0 ) {
9039         gameResult = BlackWins;
9040     }
9041
9042     if( gameMode == TwoMachinesPlay ) {
9043         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9044     }
9045 }
9046
9047
9048 // [HGM] save: calculate checksum of game to make games easily identifiable
9049 int StringCheckSum(char *s)
9050 {
9051         int i = 0;
9052         if(s==NULL) return 0;
9053         while(*s) i = i*259 + *s++;
9054         return i;
9055 }
9056
9057 int GameCheckSum()
9058 {
9059         int i, sum=0;
9060         for(i=backwardMostMove; i<forwardMostMove; i++) {
9061                 sum += pvInfoList[i].depth;
9062                 sum += StringCheckSum(parseList[i]);
9063                 sum += StringCheckSum(commentList[i]);
9064                 sum *= 261;
9065         }
9066         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9067         return sum + StringCheckSum(commentList[i]);
9068 } // end of save patch
9069
9070 void
9071 GameEnds(result, resultDetails, whosays)
9072      ChessMove result;
9073      char *resultDetails;
9074      int whosays;
9075 {
9076     GameMode nextGameMode;
9077     int isIcsGame;
9078     char buf[MSG_SIZ], popupRequested = 0;
9079
9080     if(endingGame) return; /* [HGM] crash: forbid recursion */
9081     endingGame = 1;
9082     if(twoBoards) { // [HGM] dual: switch back to one board
9083         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9084         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9085     }
9086     if (appData.debugMode) {
9087       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9088               result, resultDetails ? resultDetails : "(null)", whosays);
9089     }
9090
9091     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9092
9093     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9094         /* If we are playing on ICS, the server decides when the
9095            game is over, but the engine can offer to draw, claim
9096            a draw, or resign.
9097          */
9098 #if ZIPPY
9099         if (appData.zippyPlay && first.initDone) {
9100             if (result == GameIsDrawn) {
9101                 /* In case draw still needs to be claimed */
9102                 SendToICS(ics_prefix);
9103                 SendToICS("draw\n");
9104             } else if (StrCaseStr(resultDetails, "resign")) {
9105                 SendToICS(ics_prefix);
9106                 SendToICS("resign\n");
9107             }
9108         }
9109 #endif
9110         endingGame = 0; /* [HGM] crash */
9111         return;
9112     }
9113
9114     /* If we're loading the game from a file, stop */
9115     if (whosays == GE_FILE) {
9116       (void) StopLoadGameTimer();
9117       gameFileFP = NULL;
9118     }
9119
9120     /* Cancel draw offers */
9121     first.offeredDraw = second.offeredDraw = 0;
9122
9123     /* If this is an ICS game, only ICS can really say it's done;
9124        if not, anyone can. */
9125     isIcsGame = (gameMode == IcsPlayingWhite ||
9126                  gameMode == IcsPlayingBlack ||
9127                  gameMode == IcsObserving    ||
9128                  gameMode == IcsExamining);
9129
9130     if (!isIcsGame || whosays == GE_ICS) {
9131         /* OK -- not an ICS game, or ICS said it was done */
9132         StopClocks();
9133         if (!isIcsGame && !appData.noChessProgram)
9134           SetUserThinkingEnables();
9135
9136         /* [HGM] if a machine claims the game end we verify this claim */
9137         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9138             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9139                 char claimer;
9140                 ChessMove trueResult = (ChessMove) -1;
9141
9142                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9143                                             first.twoMachinesColor[0] :
9144                                             second.twoMachinesColor[0] ;
9145
9146                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9147                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9148                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9149                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9150                 } else
9151                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9152                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9153                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9154                 } else
9155                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9156                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9157                 }
9158
9159                 // now verify win claims, but not in drop games, as we don't understand those yet
9160                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9161                                                  || gameInfo.variant == VariantGreat) &&
9162                     (result == WhiteWins && claimer == 'w' ||
9163                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9164                       if (appData.debugMode) {
9165                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9166                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9167                       }
9168                       if(result != trueResult) {
9169                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9170                               result = claimer == 'w' ? BlackWins : WhiteWins;
9171                               resultDetails = buf;
9172                       }
9173                 } else
9174                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9175                     && (forwardMostMove <= backwardMostMove ||
9176                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9177                         (claimer=='b')==(forwardMostMove&1))
9178                                                                                   ) {
9179                       /* [HGM] verify: draws that were not flagged are false claims */
9180                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9181                       result = claimer == 'w' ? BlackWins : WhiteWins;
9182                       resultDetails = buf;
9183                 }
9184                 /* (Claiming a loss is accepted no questions asked!) */
9185             }
9186             /* [HGM] bare: don't allow bare King to win */
9187             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9188                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9189                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9190                && result != GameIsDrawn)
9191             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9192                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9193                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9194                         if(p >= 0 && p <= (int)WhiteKing) k++;
9195                 }
9196                 if (appData.debugMode) {
9197                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9198                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9199                 }
9200                 if(k <= 1) {
9201                         result = GameIsDrawn;
9202                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9203                         resultDetails = buf;
9204                 }
9205             }
9206         }
9207
9208
9209         if(serverMoves != NULL && !loadFlag) { char c = '=';
9210             if(result==WhiteWins) c = '+';
9211             if(result==BlackWins) c = '-';
9212             if(resultDetails != NULL)
9213                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9214         }
9215         if (resultDetails != NULL) {
9216             gameInfo.result = result;
9217             gameInfo.resultDetails = StrSave(resultDetails);
9218
9219             /* display last move only if game was not loaded from file */
9220             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9221                 DisplayMove(currentMove - 1);
9222
9223             if (forwardMostMove != 0) {
9224                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9225                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9226                                                                 ) {
9227                     if (*appData.saveGameFile != NULLCHAR) {
9228                         SaveGameToFile(appData.saveGameFile, TRUE);
9229                     } else if (appData.autoSaveGames) {
9230                         AutoSaveGame();
9231                     }
9232                     if (*appData.savePositionFile != NULLCHAR) {
9233                         SavePositionToFile(appData.savePositionFile);
9234                     }
9235                 }
9236             }
9237
9238             /* Tell program how game ended in case it is learning */
9239             /* [HGM] Moved this to after saving the PGN, just in case */
9240             /* engine died and we got here through time loss. In that */
9241             /* case we will get a fatal error writing the pipe, which */
9242             /* would otherwise lose us the PGN.                       */
9243             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9244             /* output during GameEnds should never be fatal anymore   */
9245             if (gameMode == MachinePlaysWhite ||
9246                 gameMode == MachinePlaysBlack ||
9247                 gameMode == TwoMachinesPlay ||
9248                 gameMode == IcsPlayingWhite ||
9249                 gameMode == IcsPlayingBlack ||
9250                 gameMode == BeginningOfGame) {
9251                 char buf[MSG_SIZ];
9252                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9253                         resultDetails);
9254                 if (first.pr != NoProc) {
9255                     SendToProgram(buf, &first);
9256                 }
9257                 if (second.pr != NoProc &&
9258                     gameMode == TwoMachinesPlay) {
9259                     SendToProgram(buf, &second);
9260                 }
9261             }
9262         }
9263
9264         if (appData.icsActive) {
9265             if (appData.quietPlay &&
9266                 (gameMode == IcsPlayingWhite ||
9267                  gameMode == IcsPlayingBlack)) {
9268                 SendToICS(ics_prefix);
9269                 SendToICS("set shout 1\n");
9270             }
9271             nextGameMode = IcsIdle;
9272             ics_user_moved = FALSE;
9273             /* clean up premove.  It's ugly when the game has ended and the
9274              * premove highlights are still on the board.
9275              */
9276             if (gotPremove) {
9277               gotPremove = FALSE;
9278               ClearPremoveHighlights();
9279               DrawPosition(FALSE, boards[currentMove]);
9280             }
9281             if (whosays == GE_ICS) {
9282                 switch (result) {
9283                 case WhiteWins:
9284                     if (gameMode == IcsPlayingWhite)
9285                         PlayIcsWinSound();
9286                     else if(gameMode == IcsPlayingBlack)
9287                         PlayIcsLossSound();
9288                     break;
9289                 case BlackWins:
9290                     if (gameMode == IcsPlayingBlack)
9291                         PlayIcsWinSound();
9292                     else if(gameMode == IcsPlayingWhite)
9293                         PlayIcsLossSound();
9294                     break;
9295                 case GameIsDrawn:
9296                     PlayIcsDrawSound();
9297                     break;
9298                 default:
9299                     PlayIcsUnfinishedSound();
9300                 }
9301             }
9302         } else if (gameMode == EditGame ||
9303                    gameMode == PlayFromGameFile ||
9304                    gameMode == AnalyzeMode ||
9305                    gameMode == AnalyzeFile) {
9306             nextGameMode = gameMode;
9307         } else {
9308             nextGameMode = EndOfGame;
9309         }
9310         pausing = FALSE;
9311         ModeHighlight();
9312     } else {
9313         nextGameMode = gameMode;
9314     }
9315
9316     if (appData.noChessProgram) {
9317         gameMode = nextGameMode;
9318         ModeHighlight();
9319         endingGame = 0; /* [HGM] crash */
9320         return;
9321     }
9322
9323     if (first.reuse) {
9324         /* Put first chess program into idle state */
9325         if (first.pr != NoProc &&
9326             (gameMode == MachinePlaysWhite ||
9327              gameMode == MachinePlaysBlack ||
9328              gameMode == TwoMachinesPlay ||
9329              gameMode == IcsPlayingWhite ||
9330              gameMode == IcsPlayingBlack ||
9331              gameMode == BeginningOfGame)) {
9332             SendToProgram("force\n", &first);
9333             if (first.usePing) {
9334               char buf[MSG_SIZ];
9335               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9336               SendToProgram(buf, &first);
9337             }
9338         }
9339     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9340         /* Kill off first chess program */
9341         if (first.isr != NULL)
9342           RemoveInputSource(first.isr);
9343         first.isr = NULL;
9344
9345         if (first.pr != NoProc) {
9346             ExitAnalyzeMode();
9347             DoSleep( appData.delayBeforeQuit );
9348             SendToProgram("quit\n", &first);
9349             DoSleep( appData.delayAfterQuit );
9350             DestroyChildProcess(first.pr, first.useSigterm);
9351         }
9352         first.pr = NoProc;
9353     }
9354     if (second.reuse) {
9355         /* Put second chess program into idle state */
9356         if (second.pr != NoProc &&
9357             gameMode == TwoMachinesPlay) {
9358             SendToProgram("force\n", &second);
9359             if (second.usePing) {
9360               char buf[MSG_SIZ];
9361               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9362               SendToProgram(buf, &second);
9363             }
9364         }
9365     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9366         /* Kill off second chess program */
9367         if (second.isr != NULL)
9368           RemoveInputSource(second.isr);
9369         second.isr = NULL;
9370
9371         if (second.pr != NoProc) {
9372             DoSleep( appData.delayBeforeQuit );
9373             SendToProgram("quit\n", &second);
9374             DoSleep( appData.delayAfterQuit );
9375             DestroyChildProcess(second.pr, second.useSigterm);
9376         }
9377         second.pr = NoProc;
9378     }
9379
9380     if (matchMode && gameMode == TwoMachinesPlay) {
9381         switch (result) {
9382         case WhiteWins:
9383           if (first.twoMachinesColor[0] == 'w') {
9384             first.matchWins++;
9385           } else {
9386             second.matchWins++;
9387           }
9388           break;
9389         case BlackWins:
9390           if (first.twoMachinesColor[0] == 'b') {
9391             first.matchWins++;
9392           } else {
9393             second.matchWins++;
9394           }
9395           break;
9396         default:
9397           break;
9398         }
9399         if (matchGame < appData.matchGames) {
9400             char *tmp;
9401             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9402                 tmp = first.twoMachinesColor;
9403                 first.twoMachinesColor = second.twoMachinesColor;
9404                 second.twoMachinesColor = tmp;
9405             }
9406             gameMode = nextGameMode;
9407             matchGame++;
9408             if(appData.matchPause>10000 || appData.matchPause<10)
9409                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9410             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9411             endingGame = 0; /* [HGM] crash */
9412             return;
9413         } else {
9414             gameMode = nextGameMode;
9415             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9416                      first.tidy, second.tidy,
9417                      first.matchWins, second.matchWins,
9418                      appData.matchGames - (first.matchWins + second.matchWins));
9419             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9420         }
9421     }
9422     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9423         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9424       ExitAnalyzeMode();
9425     gameMode = nextGameMode;
9426     ModeHighlight();
9427     endingGame = 0;  /* [HGM] crash */
9428     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9429       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9430         matchMode = FALSE; appData.matchGames = matchGame = 0;
9431         DisplayNote(buf);
9432       }
9433     }
9434 }
9435
9436 /* Assumes program was just initialized (initString sent).
9437    Leaves program in force mode. */
9438 void
9439 FeedMovesToProgram(cps, upto)
9440      ChessProgramState *cps;
9441      int upto;
9442 {
9443     int i;
9444
9445     if (appData.debugMode)
9446       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9447               startedFromSetupPosition ? "position and " : "",
9448               backwardMostMove, upto, cps->which);
9449     if(currentlyInitializedVariant != gameInfo.variant) {
9450       char buf[MSG_SIZ];
9451         // [HGM] variantswitch: make engine aware of new variant
9452         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9453                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9454         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9455         SendToProgram(buf, cps);
9456         currentlyInitializedVariant = gameInfo.variant;
9457     }
9458     SendToProgram("force\n", cps);
9459     if (startedFromSetupPosition) {
9460         SendBoard(cps, backwardMostMove);
9461     if (appData.debugMode) {
9462         fprintf(debugFP, "feedMoves\n");
9463     }
9464     }
9465     for (i = backwardMostMove; i < upto; i++) {
9466         SendMoveToProgram(i, cps);
9467     }
9468 }
9469
9470
9471 void
9472 ResurrectChessProgram()
9473 {
9474      /* The chess program may have exited.
9475         If so, restart it and feed it all the moves made so far. */
9476
9477     if (appData.noChessProgram || first.pr != NoProc) return;
9478
9479     StartChessProgram(&first);
9480     InitChessProgram(&first, FALSE);
9481     FeedMovesToProgram(&first, currentMove);
9482
9483     if (!first.sendTime) {
9484         /* can't tell gnuchess what its clock should read,
9485            so we bow to its notion. */
9486         ResetClocks();
9487         timeRemaining[0][currentMove] = whiteTimeRemaining;
9488         timeRemaining[1][currentMove] = blackTimeRemaining;
9489     }
9490
9491     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9492                 appData.icsEngineAnalyze) && first.analysisSupport) {
9493       SendToProgram("analyze\n", &first);
9494       first.analyzing = TRUE;
9495     }
9496 }
9497
9498 /*
9499  * Button procedures
9500  */
9501 void
9502 Reset(redraw, init)
9503      int redraw, init;
9504 {
9505     int i;
9506
9507     if (appData.debugMode) {
9508         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9509                 redraw, init, gameMode);
9510     }
9511     CleanupTail(); // [HGM] vari: delete any stored variations
9512     pausing = pauseExamInvalid = FALSE;
9513     startedFromSetupPosition = blackPlaysFirst = FALSE;
9514     firstMove = TRUE;
9515     whiteFlag = blackFlag = FALSE;
9516     userOfferedDraw = FALSE;
9517     hintRequested = bookRequested = FALSE;
9518     first.maybeThinking = FALSE;
9519     second.maybeThinking = FALSE;
9520     first.bookSuspend = FALSE; // [HGM] book
9521     second.bookSuspend = FALSE;
9522     thinkOutput[0] = NULLCHAR;
9523     lastHint[0] = NULLCHAR;
9524     ClearGameInfo(&gameInfo);
9525     gameInfo.variant = StringToVariant(appData.variant);
9526     ics_user_moved = ics_clock_paused = FALSE;
9527     ics_getting_history = H_FALSE;
9528     ics_gamenum = -1;
9529     white_holding[0] = black_holding[0] = NULLCHAR;
9530     ClearProgramStats();
9531     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9532
9533     ResetFrontEnd();
9534     ClearHighlights();
9535     flipView = appData.flipView;
9536     ClearPremoveHighlights();
9537     gotPremove = FALSE;
9538     alarmSounded = FALSE;
9539
9540     GameEnds(EndOfFile, NULL, GE_PLAYER);
9541     if(appData.serverMovesName != NULL) {
9542         /* [HGM] prepare to make moves file for broadcasting */
9543         clock_t t = clock();
9544         if(serverMoves != NULL) fclose(serverMoves);
9545         serverMoves = fopen(appData.serverMovesName, "r");
9546         if(serverMoves != NULL) {
9547             fclose(serverMoves);
9548             /* delay 15 sec before overwriting, so all clients can see end */
9549             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9550         }
9551         serverMoves = fopen(appData.serverMovesName, "w");
9552     }
9553
9554     ExitAnalyzeMode();
9555     gameMode = BeginningOfGame;
9556     ModeHighlight();
9557     if(appData.icsActive) gameInfo.variant = VariantNormal;
9558     currentMove = forwardMostMove = backwardMostMove = 0;
9559     InitPosition(redraw);
9560     for (i = 0; i < MAX_MOVES; i++) {
9561         if (commentList[i] != NULL) {
9562             free(commentList[i]);
9563             commentList[i] = NULL;
9564         }
9565     }
9566     ResetClocks();
9567     timeRemaining[0][0] = whiteTimeRemaining;
9568     timeRemaining[1][0] = blackTimeRemaining;
9569     if (first.pr == NULL) {
9570         StartChessProgram(&first);
9571     }
9572     if (init) {
9573             InitChessProgram(&first, startedFromSetupPosition);
9574     }
9575     DisplayTitle("");
9576     DisplayMessage("", "");
9577     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9578     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9579 }
9580
9581 void
9582 AutoPlayGameLoop()
9583 {
9584     for (;;) {
9585         if (!AutoPlayOneMove())
9586           return;
9587         if (matchMode || appData.timeDelay == 0)
9588           continue;
9589         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9590           return;
9591         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9592         break;
9593     }
9594 }
9595
9596
9597 int
9598 AutoPlayOneMove()
9599 {
9600     int fromX, fromY, toX, toY;
9601
9602     if (appData.debugMode) {
9603       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9604     }
9605
9606     if (gameMode != PlayFromGameFile)
9607       return FALSE;
9608
9609     if (currentMove >= forwardMostMove) {
9610       gameMode = EditGame;
9611       ModeHighlight();
9612
9613       /* [AS] Clear current move marker at the end of a game */
9614       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9615
9616       return FALSE;
9617     }
9618
9619     toX = moveList[currentMove][2] - AAA;
9620     toY = moveList[currentMove][3] - ONE;
9621
9622     if (moveList[currentMove][1] == '@') {
9623         if (appData.highlightLastMove) {
9624             SetHighlights(-1, -1, toX, toY);
9625         }
9626     } else {
9627         fromX = moveList[currentMove][0] - AAA;
9628         fromY = moveList[currentMove][1] - ONE;
9629
9630         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9631
9632         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9633
9634         if (appData.highlightLastMove) {
9635             SetHighlights(fromX, fromY, toX, toY);
9636         }
9637     }
9638     DisplayMove(currentMove);
9639     SendMoveToProgram(currentMove++, &first);
9640     DisplayBothClocks();
9641     DrawPosition(FALSE, boards[currentMove]);
9642     // [HGM] PV info: always display, routine tests if empty
9643     DisplayComment(currentMove - 1, commentList[currentMove]);
9644     return TRUE;
9645 }
9646
9647
9648 int
9649 LoadGameOneMove(readAhead)
9650      ChessMove readAhead;
9651 {
9652     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9653     char promoChar = NULLCHAR;
9654     ChessMove moveType;
9655     char move[MSG_SIZ];
9656     char *p, *q;
9657
9658     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9659         gameMode != AnalyzeMode && gameMode != Training) {
9660         gameFileFP = NULL;
9661         return FALSE;
9662     }
9663
9664     yyboardindex = forwardMostMove;
9665     if (readAhead != EndOfFile) {
9666       moveType = readAhead;
9667     } else {
9668       if (gameFileFP == NULL)
9669           return FALSE;
9670       moveType = (ChessMove) Myylex();
9671     }
9672
9673     done = FALSE;
9674     switch (moveType) {
9675       case Comment:
9676         if (appData.debugMode)
9677           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9678         p = yy_text;
9679
9680         /* append the comment but don't display it */
9681         AppendComment(currentMove, p, FALSE);
9682         return TRUE;
9683
9684       case WhiteCapturesEnPassant:
9685       case BlackCapturesEnPassant:
9686       case WhitePromotion:
9687       case BlackPromotion:
9688       case WhiteNonPromotion:
9689       case BlackNonPromotion:
9690       case NormalMove:
9691       case WhiteKingSideCastle:
9692       case WhiteQueenSideCastle:
9693       case BlackKingSideCastle:
9694       case BlackQueenSideCastle:
9695       case WhiteKingSideCastleWild:
9696       case WhiteQueenSideCastleWild:
9697       case BlackKingSideCastleWild:
9698       case BlackQueenSideCastleWild:
9699       /* PUSH Fabien */
9700       case WhiteHSideCastleFR:
9701       case WhiteASideCastleFR:
9702       case BlackHSideCastleFR:
9703       case BlackASideCastleFR:
9704       /* POP Fabien */
9705         if (appData.debugMode)
9706           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9707         fromX = currentMoveString[0] - AAA;
9708         fromY = currentMoveString[1] - ONE;
9709         toX = currentMoveString[2] - AAA;
9710         toY = currentMoveString[3] - ONE;
9711         promoChar = currentMoveString[4];
9712         break;
9713
9714       case WhiteDrop:
9715       case BlackDrop:
9716         if (appData.debugMode)
9717           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9718         fromX = moveType == WhiteDrop ?
9719           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9720         (int) CharToPiece(ToLower(currentMoveString[0]));
9721         fromY = DROP_RANK;
9722         toX = currentMoveString[2] - AAA;
9723         toY = currentMoveString[3] - ONE;
9724         break;
9725
9726       case WhiteWins:
9727       case BlackWins:
9728       case GameIsDrawn:
9729       case GameUnfinished:
9730         if (appData.debugMode)
9731           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9732         p = strchr(yy_text, '{');
9733         if (p == NULL) p = strchr(yy_text, '(');
9734         if (p == NULL) {
9735             p = yy_text;
9736             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9737         } else {
9738             q = strchr(p, *p == '{' ? '}' : ')');
9739             if (q != NULL) *q = NULLCHAR;
9740             p++;
9741         }
9742         GameEnds(moveType, p, GE_FILE);
9743         done = TRUE;
9744         if (cmailMsgLoaded) {
9745             ClearHighlights();
9746             flipView = WhiteOnMove(currentMove);
9747             if (moveType == GameUnfinished) flipView = !flipView;
9748             if (appData.debugMode)
9749               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9750         }
9751         break;
9752
9753       case EndOfFile:
9754         if (appData.debugMode)
9755           fprintf(debugFP, "Parser hit end of file\n");
9756         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9757           case MT_NONE:
9758           case MT_CHECK:
9759             break;
9760           case MT_CHECKMATE:
9761           case MT_STAINMATE:
9762             if (WhiteOnMove(currentMove)) {
9763                 GameEnds(BlackWins, "Black mates", GE_FILE);
9764             } else {
9765                 GameEnds(WhiteWins, "White mates", GE_FILE);
9766             }
9767             break;
9768           case MT_STALEMATE:
9769             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9770             break;
9771         }
9772         done = TRUE;
9773         break;
9774
9775       case MoveNumberOne:
9776         if (lastLoadGameStart == GNUChessGame) {
9777             /* GNUChessGames have numbers, but they aren't move numbers */
9778             if (appData.debugMode)
9779               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9780                       yy_text, (int) moveType);
9781             return LoadGameOneMove(EndOfFile); /* tail recursion */
9782         }
9783         /* else fall thru */
9784
9785       case XBoardGame:
9786       case GNUChessGame:
9787       case PGNTag:
9788         /* Reached start of next game in file */
9789         if (appData.debugMode)
9790           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9791         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9792           case MT_NONE:
9793           case MT_CHECK:
9794             break;
9795           case MT_CHECKMATE:
9796           case MT_STAINMATE:
9797             if (WhiteOnMove(currentMove)) {
9798                 GameEnds(BlackWins, "Black mates", GE_FILE);
9799             } else {
9800                 GameEnds(WhiteWins, "White mates", GE_FILE);
9801             }
9802             break;
9803           case MT_STALEMATE:
9804             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9805             break;
9806         }
9807         done = TRUE;
9808         break;
9809
9810       case PositionDiagram:     /* should not happen; ignore */
9811       case ElapsedTime:         /* ignore */
9812       case NAG:                 /* ignore */
9813         if (appData.debugMode)
9814           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9815                   yy_text, (int) moveType);
9816         return LoadGameOneMove(EndOfFile); /* tail recursion */
9817
9818       case IllegalMove:
9819         if (appData.testLegality) {
9820             if (appData.debugMode)
9821               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9822             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9823                     (forwardMostMove / 2) + 1,
9824                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9825             DisplayError(move, 0);
9826             done = TRUE;
9827         } else {
9828             if (appData.debugMode)
9829               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9830                       yy_text, currentMoveString);
9831             fromX = currentMoveString[0] - AAA;
9832             fromY = currentMoveString[1] - ONE;
9833             toX = currentMoveString[2] - AAA;
9834             toY = currentMoveString[3] - ONE;
9835             promoChar = currentMoveString[4];
9836         }
9837         break;
9838
9839       case AmbiguousMove:
9840         if (appData.debugMode)
9841           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9842         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9843                 (forwardMostMove / 2) + 1,
9844                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9845         DisplayError(move, 0);
9846         done = TRUE;
9847         break;
9848
9849       default:
9850       case ImpossibleMove:
9851         if (appData.debugMode)
9852           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9853         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9854                 (forwardMostMove / 2) + 1,
9855                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9856         DisplayError(move, 0);
9857         done = TRUE;
9858         break;
9859     }
9860
9861     if (done) {
9862         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9863             DrawPosition(FALSE, boards[currentMove]);
9864             DisplayBothClocks();
9865             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9866               DisplayComment(currentMove - 1, commentList[currentMove]);
9867         }
9868         (void) StopLoadGameTimer();
9869         gameFileFP = NULL;
9870         cmailOldMove = forwardMostMove;
9871         return FALSE;
9872     } else {
9873         /* currentMoveString is set as a side-effect of yylex */
9874         strcat(currentMoveString, "\n");
9875         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9876
9877         thinkOutput[0] = NULLCHAR;
9878         MakeMove(fromX, fromY, toX, toY, promoChar);
9879         currentMove = forwardMostMove;
9880         return TRUE;
9881     }
9882 }
9883
9884 /* Load the nth game from the given file */
9885 int
9886 LoadGameFromFile(filename, n, title, useList)
9887      char *filename;
9888      int n;
9889      char *title;
9890      /*Boolean*/ int useList;
9891 {
9892     FILE *f;
9893     char buf[MSG_SIZ];
9894
9895     if (strcmp(filename, "-") == 0) {
9896         f = stdin;
9897         title = "stdin";
9898     } else {
9899         f = fopen(filename, "rb");
9900         if (f == NULL) {
9901           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9902             DisplayError(buf, errno);
9903             return FALSE;
9904         }
9905     }
9906     if (fseek(f, 0, 0) == -1) {
9907         /* f is not seekable; probably a pipe */
9908         useList = FALSE;
9909     }
9910     if (useList && n == 0) {
9911         int error = GameListBuild(f);
9912         if (error) {
9913             DisplayError(_("Cannot build game list"), error);
9914         } else if (!ListEmpty(&gameList) &&
9915                    ((ListGame *) gameList.tailPred)->number > 1) {
9916             GameListPopUp(f, title);
9917             return TRUE;
9918         }
9919         GameListDestroy();
9920         n = 1;
9921     }
9922     if (n == 0) n = 1;
9923     return LoadGame(f, n, title, FALSE);
9924 }
9925
9926
9927 void
9928 MakeRegisteredMove()
9929 {
9930     int fromX, fromY, toX, toY;
9931     char promoChar;
9932     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9933         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9934           case CMAIL_MOVE:
9935           case CMAIL_DRAW:
9936             if (appData.debugMode)
9937               fprintf(debugFP, "Restoring %s for game %d\n",
9938                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9939
9940             thinkOutput[0] = NULLCHAR;
9941             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9942             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9943             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9944             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9945             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9946             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9947             MakeMove(fromX, fromY, toX, toY, promoChar);
9948             ShowMove(fromX, fromY, toX, toY);
9949
9950             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9951               case MT_NONE:
9952               case MT_CHECK:
9953                 break;
9954
9955               case MT_CHECKMATE:
9956               case MT_STAINMATE:
9957                 if (WhiteOnMove(currentMove)) {
9958                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9959                 } else {
9960                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9961                 }
9962                 break;
9963
9964               case MT_STALEMATE:
9965                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9966                 break;
9967             }
9968
9969             break;
9970
9971           case CMAIL_RESIGN:
9972             if (WhiteOnMove(currentMove)) {
9973                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9974             } else {
9975                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9976             }
9977             break;
9978
9979           case CMAIL_ACCEPT:
9980             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9981             break;
9982
9983           default:
9984             break;
9985         }
9986     }
9987
9988     return;
9989 }
9990
9991 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9992 int
9993 CmailLoadGame(f, gameNumber, title, useList)
9994      FILE *f;
9995      int gameNumber;
9996      char *title;
9997      int useList;
9998 {
9999     int retVal;
10000
10001     if (gameNumber > nCmailGames) {
10002         DisplayError(_("No more games in this message"), 0);
10003         return FALSE;
10004     }
10005     if (f == lastLoadGameFP) {
10006         int offset = gameNumber - lastLoadGameNumber;
10007         if (offset == 0) {
10008             cmailMsg[0] = NULLCHAR;
10009             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10010                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10011                 nCmailMovesRegistered--;
10012             }
10013             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10014             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10015                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10016             }
10017         } else {
10018             if (! RegisterMove()) return FALSE;
10019         }
10020     }
10021
10022     retVal = LoadGame(f, gameNumber, title, useList);
10023
10024     /* Make move registered during previous look at this game, if any */
10025     MakeRegisteredMove();
10026
10027     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10028         commentList[currentMove]
10029           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10030         DisplayComment(currentMove - 1, commentList[currentMove]);
10031     }
10032
10033     return retVal;
10034 }
10035
10036 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10037 int
10038 ReloadGame(offset)
10039      int offset;
10040 {
10041     int gameNumber = lastLoadGameNumber + offset;
10042     if (lastLoadGameFP == NULL) {
10043         DisplayError(_("No game has been loaded yet"), 0);
10044         return FALSE;
10045     }
10046     if (gameNumber <= 0) {
10047         DisplayError(_("Can't back up any further"), 0);
10048         return FALSE;
10049     }
10050     if (cmailMsgLoaded) {
10051         return CmailLoadGame(lastLoadGameFP, gameNumber,
10052                              lastLoadGameTitle, lastLoadGameUseList);
10053     } else {
10054         return LoadGame(lastLoadGameFP, gameNumber,
10055                         lastLoadGameTitle, lastLoadGameUseList);
10056     }
10057 }
10058
10059
10060
10061 /* Load the nth game from open file f */
10062 int
10063 LoadGame(f, gameNumber, title, useList)
10064      FILE *f;
10065      int gameNumber;
10066      char *title;
10067      int useList;
10068 {
10069     ChessMove cm;
10070     char buf[MSG_SIZ];
10071     int gn = gameNumber;
10072     ListGame *lg = NULL;
10073     int numPGNTags = 0;
10074     int err;
10075     GameMode oldGameMode;
10076     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10077
10078     if (appData.debugMode)
10079         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10080
10081     if (gameMode == Training )
10082         SetTrainingModeOff();
10083
10084     oldGameMode = gameMode;
10085     if (gameMode != BeginningOfGame) {
10086       Reset(FALSE, TRUE);
10087     }
10088
10089     gameFileFP = f;
10090     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10091         fclose(lastLoadGameFP);
10092     }
10093
10094     if (useList) {
10095         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10096
10097         if (lg) {
10098             fseek(f, lg->offset, 0);
10099             GameListHighlight(gameNumber);
10100             gn = 1;
10101         }
10102         else {
10103             DisplayError(_("Game number out of range"), 0);
10104             return FALSE;
10105         }
10106     } else {
10107         GameListDestroy();
10108         if (fseek(f, 0, 0) == -1) {
10109             if (f == lastLoadGameFP ?
10110                 gameNumber == lastLoadGameNumber + 1 :
10111                 gameNumber == 1) {
10112                 gn = 1;
10113             } else {
10114                 DisplayError(_("Can't seek on game file"), 0);
10115                 return FALSE;
10116             }
10117         }
10118     }
10119     lastLoadGameFP = f;
10120     lastLoadGameNumber = gameNumber;
10121     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10122     lastLoadGameUseList = useList;
10123
10124     yynewfile(f);
10125
10126     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10127       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10128                 lg->gameInfo.black);
10129             DisplayTitle(buf);
10130     } else if (*title != NULLCHAR) {
10131         if (gameNumber > 1) {
10132           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10133             DisplayTitle(buf);
10134         } else {
10135             DisplayTitle(title);
10136         }
10137     }
10138
10139     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10140         gameMode = PlayFromGameFile;
10141         ModeHighlight();
10142     }
10143
10144     currentMove = forwardMostMove = backwardMostMove = 0;
10145     CopyBoard(boards[0], initialPosition);
10146     StopClocks();
10147
10148     /*
10149      * Skip the first gn-1 games in the file.
10150      * Also skip over anything that precedes an identifiable
10151      * start of game marker, to avoid being confused by
10152      * garbage at the start of the file.  Currently
10153      * recognized start of game markers are the move number "1",
10154      * the pattern "gnuchess .* game", the pattern
10155      * "^[#;%] [^ ]* game file", and a PGN tag block.
10156      * A game that starts with one of the latter two patterns
10157      * will also have a move number 1, possibly
10158      * following a position diagram.
10159      * 5-4-02: Let's try being more lenient and allowing a game to
10160      * start with an unnumbered move.  Does that break anything?
10161      */
10162     cm = lastLoadGameStart = EndOfFile;
10163     while (gn > 0) {
10164         yyboardindex = forwardMostMove;
10165         cm = (ChessMove) Myylex();
10166         switch (cm) {
10167           case EndOfFile:
10168             if (cmailMsgLoaded) {
10169                 nCmailGames = CMAIL_MAX_GAMES - gn;
10170             } else {
10171                 Reset(TRUE, TRUE);
10172                 DisplayError(_("Game not found in file"), 0);
10173             }
10174             return FALSE;
10175
10176           case GNUChessGame:
10177           case XBoardGame:
10178             gn--;
10179             lastLoadGameStart = cm;
10180             break;
10181
10182           case MoveNumberOne:
10183             switch (lastLoadGameStart) {
10184               case GNUChessGame:
10185               case XBoardGame:
10186               case PGNTag:
10187                 break;
10188               case MoveNumberOne:
10189               case EndOfFile:
10190                 gn--;           /* count this game */
10191                 lastLoadGameStart = cm;
10192                 break;
10193               default:
10194                 /* impossible */
10195                 break;
10196             }
10197             break;
10198
10199           case PGNTag:
10200             switch (lastLoadGameStart) {
10201               case GNUChessGame:
10202               case PGNTag:
10203               case MoveNumberOne:
10204               case EndOfFile:
10205                 gn--;           /* count this game */
10206                 lastLoadGameStart = cm;
10207                 break;
10208               case XBoardGame:
10209                 lastLoadGameStart = cm; /* game counted already */
10210                 break;
10211               default:
10212                 /* impossible */
10213                 break;
10214             }
10215             if (gn > 0) {
10216                 do {
10217                     yyboardindex = forwardMostMove;
10218                     cm = (ChessMove) Myylex();
10219                 } while (cm == PGNTag || cm == Comment);
10220             }
10221             break;
10222
10223           case WhiteWins:
10224           case BlackWins:
10225           case GameIsDrawn:
10226             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10227                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10228                     != CMAIL_OLD_RESULT) {
10229                     nCmailResults ++ ;
10230                     cmailResult[  CMAIL_MAX_GAMES
10231                                 - gn - 1] = CMAIL_OLD_RESULT;
10232                 }
10233             }
10234             break;
10235
10236           case NormalMove:
10237             /* Only a NormalMove can be at the start of a game
10238              * without a position diagram. */
10239             if (lastLoadGameStart == EndOfFile ) {
10240               gn--;
10241               lastLoadGameStart = MoveNumberOne;
10242             }
10243             break;
10244
10245           default:
10246             break;
10247         }
10248     }
10249
10250     if (appData.debugMode)
10251       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10252
10253     if (cm == XBoardGame) {
10254         /* Skip any header junk before position diagram and/or move 1 */
10255         for (;;) {
10256             yyboardindex = forwardMostMove;
10257             cm = (ChessMove) Myylex();
10258
10259             if (cm == EndOfFile ||
10260                 cm == GNUChessGame || cm == XBoardGame) {
10261                 /* Empty game; pretend end-of-file and handle later */
10262                 cm = EndOfFile;
10263                 break;
10264             }
10265
10266             if (cm == MoveNumberOne || cm == PositionDiagram ||
10267                 cm == PGNTag || cm == Comment)
10268               break;
10269         }
10270     } else if (cm == GNUChessGame) {
10271         if (gameInfo.event != NULL) {
10272             free(gameInfo.event);
10273         }
10274         gameInfo.event = StrSave(yy_text);
10275     }
10276
10277     startedFromSetupPosition = FALSE;
10278     while (cm == PGNTag) {
10279         if (appData.debugMode)
10280           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10281         err = ParsePGNTag(yy_text, &gameInfo);
10282         if (!err) numPGNTags++;
10283
10284         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10285         if(gameInfo.variant != oldVariant) {
10286             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10287             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10288             InitPosition(TRUE);
10289             oldVariant = gameInfo.variant;
10290             if (appData.debugMode)
10291               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10292         }
10293
10294
10295         if (gameInfo.fen != NULL) {
10296           Board initial_position;
10297           startedFromSetupPosition = TRUE;
10298           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10299             Reset(TRUE, TRUE);
10300             DisplayError(_("Bad FEN position in file"), 0);
10301             return FALSE;
10302           }
10303           CopyBoard(boards[0], initial_position);
10304           if (blackPlaysFirst) {
10305             currentMove = forwardMostMove = backwardMostMove = 1;
10306             CopyBoard(boards[1], initial_position);
10307             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10308             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10309             timeRemaining[0][1] = whiteTimeRemaining;
10310             timeRemaining[1][1] = blackTimeRemaining;
10311             if (commentList[0] != NULL) {
10312               commentList[1] = commentList[0];
10313               commentList[0] = NULL;
10314             }
10315           } else {
10316             currentMove = forwardMostMove = backwardMostMove = 0;
10317           }
10318           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10319           {   int i;
10320               initialRulePlies = FENrulePlies;
10321               for( i=0; i< nrCastlingRights; i++ )
10322                   initialRights[i] = initial_position[CASTLING][i];
10323           }
10324           yyboardindex = forwardMostMove;
10325           free(gameInfo.fen);
10326           gameInfo.fen = NULL;
10327         }
10328
10329         yyboardindex = forwardMostMove;
10330         cm = (ChessMove) Myylex();
10331
10332         /* Handle comments interspersed among the tags */
10333         while (cm == Comment) {
10334             char *p;
10335             if (appData.debugMode)
10336               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10337             p = yy_text;
10338             AppendComment(currentMove, p, FALSE);
10339             yyboardindex = forwardMostMove;
10340             cm = (ChessMove) Myylex();
10341         }
10342     }
10343
10344     /* don't rely on existence of Event tag since if game was
10345      * pasted from clipboard the Event tag may not exist
10346      */
10347     if (numPGNTags > 0){
10348         char *tags;
10349         if (gameInfo.variant == VariantNormal) {
10350           VariantClass v = StringToVariant(gameInfo.event);
10351           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10352           if(v < VariantShogi) gameInfo.variant = v;
10353         }
10354         if (!matchMode) {
10355           if( appData.autoDisplayTags ) {
10356             tags = PGNTags(&gameInfo);
10357             TagsPopUp(tags, CmailMsg());
10358             free(tags);
10359           }
10360         }
10361     } else {
10362         /* Make something up, but don't display it now */
10363         SetGameInfo();
10364         TagsPopDown();
10365     }
10366
10367     if (cm == PositionDiagram) {
10368         int i, j;
10369         char *p;
10370         Board initial_position;
10371
10372         if (appData.debugMode)
10373           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10374
10375         if (!startedFromSetupPosition) {
10376             p = yy_text;
10377             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10378               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10379                 switch (*p) {
10380                   case '[':
10381                   case '-':
10382                   case ' ':
10383                   case '\t':
10384                   case '\n':
10385                   case '\r':
10386                     break;
10387                   default:
10388                     initial_position[i][j++] = CharToPiece(*p);
10389                     break;
10390                 }
10391             while (*p == ' ' || *p == '\t' ||
10392                    *p == '\n' || *p == '\r') p++;
10393
10394             if (strncmp(p, "black", strlen("black"))==0)
10395               blackPlaysFirst = TRUE;
10396             else
10397               blackPlaysFirst = FALSE;
10398             startedFromSetupPosition = TRUE;
10399
10400             CopyBoard(boards[0], initial_position);
10401             if (blackPlaysFirst) {
10402                 currentMove = forwardMostMove = backwardMostMove = 1;
10403                 CopyBoard(boards[1], initial_position);
10404                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10405                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10406                 timeRemaining[0][1] = whiteTimeRemaining;
10407                 timeRemaining[1][1] = blackTimeRemaining;
10408                 if (commentList[0] != NULL) {
10409                     commentList[1] = commentList[0];
10410                     commentList[0] = NULL;
10411                 }
10412             } else {
10413                 currentMove = forwardMostMove = backwardMostMove = 0;
10414             }
10415         }
10416         yyboardindex = forwardMostMove;
10417         cm = (ChessMove) Myylex();
10418     }
10419
10420     if (first.pr == NoProc) {
10421         StartChessProgram(&first);
10422     }
10423     InitChessProgram(&first, FALSE);
10424     SendToProgram("force\n", &first);
10425     if (startedFromSetupPosition) {
10426         SendBoard(&first, forwardMostMove);
10427     if (appData.debugMode) {
10428         fprintf(debugFP, "Load Game\n");
10429     }
10430         DisplayBothClocks();
10431     }
10432
10433     /* [HGM] server: flag to write setup moves in broadcast file as one */
10434     loadFlag = appData.suppressLoadMoves;
10435
10436     while (cm == Comment) {
10437         char *p;
10438         if (appData.debugMode)
10439           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10440         p = yy_text;
10441         AppendComment(currentMove, p, FALSE);
10442         yyboardindex = forwardMostMove;
10443         cm = (ChessMove) Myylex();
10444     }
10445
10446     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10447         cm == WhiteWins || cm == BlackWins ||
10448         cm == GameIsDrawn || cm == GameUnfinished) {
10449         DisplayMessage("", _("No moves in game"));
10450         if (cmailMsgLoaded) {
10451             if (appData.debugMode)
10452               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10453             ClearHighlights();
10454             flipView = FALSE;
10455         }
10456         DrawPosition(FALSE, boards[currentMove]);
10457         DisplayBothClocks();
10458         gameMode = EditGame;
10459         ModeHighlight();
10460         gameFileFP = NULL;
10461         cmailOldMove = 0;
10462         return TRUE;
10463     }
10464
10465     // [HGM] PV info: routine tests if comment empty
10466     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10467         DisplayComment(currentMove - 1, commentList[currentMove]);
10468     }
10469     if (!matchMode && appData.timeDelay != 0)
10470       DrawPosition(FALSE, boards[currentMove]);
10471
10472     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10473       programStats.ok_to_send = 1;
10474     }
10475
10476     /* if the first token after the PGN tags is a move
10477      * and not move number 1, retrieve it from the parser
10478      */
10479     if (cm != MoveNumberOne)
10480         LoadGameOneMove(cm);
10481
10482     /* load the remaining moves from the file */
10483     while (LoadGameOneMove(EndOfFile)) {
10484       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10485       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10486     }
10487
10488     /* rewind to the start of the game */
10489     currentMove = backwardMostMove;
10490
10491     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10492
10493     if (oldGameMode == AnalyzeFile ||
10494         oldGameMode == AnalyzeMode) {
10495       AnalyzeFileEvent();
10496     }
10497
10498     if (matchMode || appData.timeDelay == 0) {
10499       ToEndEvent();
10500       gameMode = EditGame;
10501       ModeHighlight();
10502     } else if (appData.timeDelay > 0) {
10503       AutoPlayGameLoop();
10504     }
10505
10506     if (appData.debugMode)
10507         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10508
10509     loadFlag = 0; /* [HGM] true game starts */
10510     return TRUE;
10511 }
10512
10513 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10514 int
10515 ReloadPosition(offset)
10516      int offset;
10517 {
10518     int positionNumber = lastLoadPositionNumber + offset;
10519     if (lastLoadPositionFP == NULL) {
10520         DisplayError(_("No position has been loaded yet"), 0);
10521         return FALSE;
10522     }
10523     if (positionNumber <= 0) {
10524         DisplayError(_("Can't back up any further"), 0);
10525         return FALSE;
10526     }
10527     return LoadPosition(lastLoadPositionFP, positionNumber,
10528                         lastLoadPositionTitle);
10529 }
10530
10531 /* Load the nth position from the given file */
10532 int
10533 LoadPositionFromFile(filename, n, title)
10534      char *filename;
10535      int n;
10536      char *title;
10537 {
10538     FILE *f;
10539     char buf[MSG_SIZ];
10540
10541     if (strcmp(filename, "-") == 0) {
10542         return LoadPosition(stdin, n, "stdin");
10543     } else {
10544         f = fopen(filename, "rb");
10545         if (f == NULL) {
10546             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10547             DisplayError(buf, errno);
10548             return FALSE;
10549         } else {
10550             return LoadPosition(f, n, title);
10551         }
10552     }
10553 }
10554
10555 /* Load the nth position from the given open file, and close it */
10556 int
10557 LoadPosition(f, positionNumber, title)
10558      FILE *f;
10559      int positionNumber;
10560      char *title;
10561 {
10562     char *p, line[MSG_SIZ];
10563     Board initial_position;
10564     int i, j, fenMode, pn;
10565
10566     if (gameMode == Training )
10567         SetTrainingModeOff();
10568
10569     if (gameMode != BeginningOfGame) {
10570         Reset(FALSE, TRUE);
10571     }
10572     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10573         fclose(lastLoadPositionFP);
10574     }
10575     if (positionNumber == 0) positionNumber = 1;
10576     lastLoadPositionFP = f;
10577     lastLoadPositionNumber = positionNumber;
10578     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10579     if (first.pr == NoProc) {
10580       StartChessProgram(&first);
10581       InitChessProgram(&first, FALSE);
10582     }
10583     pn = positionNumber;
10584     if (positionNumber < 0) {
10585         /* Negative position number means to seek to that byte offset */
10586         if (fseek(f, -positionNumber, 0) == -1) {
10587             DisplayError(_("Can't seek on position file"), 0);
10588             return FALSE;
10589         };
10590         pn = 1;
10591     } else {
10592         if (fseek(f, 0, 0) == -1) {
10593             if (f == lastLoadPositionFP ?
10594                 positionNumber == lastLoadPositionNumber + 1 :
10595                 positionNumber == 1) {
10596                 pn = 1;
10597             } else {
10598                 DisplayError(_("Can't seek on position file"), 0);
10599                 return FALSE;
10600             }
10601         }
10602     }
10603     /* See if this file is FEN or old-style xboard */
10604     if (fgets(line, MSG_SIZ, f) == NULL) {
10605         DisplayError(_("Position not found in file"), 0);
10606         return FALSE;
10607     }
10608     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10609     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10610
10611     if (pn >= 2) {
10612         if (fenMode || line[0] == '#') pn--;
10613         while (pn > 0) {
10614             /* skip positions before number pn */
10615             if (fgets(line, MSG_SIZ, f) == NULL) {
10616                 Reset(TRUE, TRUE);
10617                 DisplayError(_("Position not found in file"), 0);
10618                 return FALSE;
10619             }
10620             if (fenMode || line[0] == '#') pn--;
10621         }
10622     }
10623
10624     if (fenMode) {
10625         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10626             DisplayError(_("Bad FEN position in file"), 0);
10627             return FALSE;
10628         }
10629     } else {
10630         (void) fgets(line, MSG_SIZ, f);
10631         (void) fgets(line, MSG_SIZ, f);
10632
10633         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10634             (void) fgets(line, MSG_SIZ, f);
10635             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10636                 if (*p == ' ')
10637                   continue;
10638                 initial_position[i][j++] = CharToPiece(*p);
10639             }
10640         }
10641
10642         blackPlaysFirst = FALSE;
10643         if (!feof(f)) {
10644             (void) fgets(line, MSG_SIZ, f);
10645             if (strncmp(line, "black", strlen("black"))==0)
10646               blackPlaysFirst = TRUE;
10647         }
10648     }
10649     startedFromSetupPosition = TRUE;
10650
10651     SendToProgram("force\n", &first);
10652     CopyBoard(boards[0], initial_position);
10653     if (blackPlaysFirst) {
10654         currentMove = forwardMostMove = backwardMostMove = 1;
10655         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10656         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10657         CopyBoard(boards[1], initial_position);
10658         DisplayMessage("", _("Black to play"));
10659     } else {
10660         currentMove = forwardMostMove = backwardMostMove = 0;
10661         DisplayMessage("", _("White to play"));
10662     }
10663     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10664     SendBoard(&first, forwardMostMove);
10665     if (appData.debugMode) {
10666 int i, j;
10667   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10668   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10669         fprintf(debugFP, "Load Position\n");
10670     }
10671
10672     if (positionNumber > 1) {
10673       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10674         DisplayTitle(line);
10675     } else {
10676         DisplayTitle(title);
10677     }
10678     gameMode = EditGame;
10679     ModeHighlight();
10680     ResetClocks();
10681     timeRemaining[0][1] = whiteTimeRemaining;
10682     timeRemaining[1][1] = blackTimeRemaining;
10683     DrawPosition(FALSE, boards[currentMove]);
10684
10685     return TRUE;
10686 }
10687
10688
10689 void
10690 CopyPlayerNameIntoFileName(dest, src)
10691      char **dest, *src;
10692 {
10693     while (*src != NULLCHAR && *src != ',') {
10694         if (*src == ' ') {
10695             *(*dest)++ = '_';
10696             src++;
10697         } else {
10698             *(*dest)++ = *src++;
10699         }
10700     }
10701 }
10702
10703 char *DefaultFileName(ext)
10704      char *ext;
10705 {
10706     static char def[MSG_SIZ];
10707     char *p;
10708
10709     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10710         p = def;
10711         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10712         *p++ = '-';
10713         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10714         *p++ = '.';
10715         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10716     } else {
10717         def[0] = NULLCHAR;
10718     }
10719     return def;
10720 }
10721
10722 /* Save the current game to the given file */
10723 int
10724 SaveGameToFile(filename, append)
10725      char *filename;
10726      int append;
10727 {
10728     FILE *f;
10729     char buf[MSG_SIZ];
10730
10731     if (strcmp(filename, "-") == 0) {
10732         return SaveGame(stdout, 0, NULL);
10733     } else {
10734         f = fopen(filename, append ? "a" : "w");
10735         if (f == NULL) {
10736             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10737             DisplayError(buf, errno);
10738             return FALSE;
10739         } else {
10740             return SaveGame(f, 0, NULL);
10741         }
10742     }
10743 }
10744
10745 char *
10746 SavePart(str)
10747      char *str;
10748 {
10749     static char buf[MSG_SIZ];
10750     char *p;
10751
10752     p = strchr(str, ' ');
10753     if (p == NULL) return str;
10754     strncpy(buf, str, p - str);
10755     buf[p - str] = NULLCHAR;
10756     return buf;
10757 }
10758
10759 #define PGN_MAX_LINE 75
10760
10761 #define PGN_SIDE_WHITE  0
10762 #define PGN_SIDE_BLACK  1
10763
10764 /* [AS] */
10765 static int FindFirstMoveOutOfBook( int side )
10766 {
10767     int result = -1;
10768
10769     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10770         int index = backwardMostMove;
10771         int has_book_hit = 0;
10772
10773         if( (index % 2) != side ) {
10774             index++;
10775         }
10776
10777         while( index < forwardMostMove ) {
10778             /* Check to see if engine is in book */
10779             int depth = pvInfoList[index].depth;
10780             int score = pvInfoList[index].score;
10781             int in_book = 0;
10782
10783             if( depth <= 2 ) {
10784                 in_book = 1;
10785             }
10786             else if( score == 0 && depth == 63 ) {
10787                 in_book = 1; /* Zappa */
10788             }
10789             else if( score == 2 && depth == 99 ) {
10790                 in_book = 1; /* Abrok */
10791             }
10792
10793             has_book_hit += in_book;
10794
10795             if( ! in_book ) {
10796                 result = index;
10797
10798                 break;
10799             }
10800
10801             index += 2;
10802         }
10803     }
10804
10805     return result;
10806 }
10807
10808 /* [AS] */
10809 void GetOutOfBookInfo( char * buf )
10810 {
10811     int oob[2];
10812     int i;
10813     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10814
10815     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10816     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10817
10818     *buf = '\0';
10819
10820     if( oob[0] >= 0 || oob[1] >= 0 ) {
10821         for( i=0; i<2; i++ ) {
10822             int idx = oob[i];
10823
10824             if( idx >= 0 ) {
10825                 if( i > 0 && oob[0] >= 0 ) {
10826                     strcat( buf, "   " );
10827                 }
10828
10829                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10830                 sprintf( buf+strlen(buf), "%s%.2f",
10831                     pvInfoList[idx].score >= 0 ? "+" : "",
10832                     pvInfoList[idx].score / 100.0 );
10833             }
10834         }
10835     }
10836 }
10837
10838 /* Save game in PGN style and close the file */
10839 int
10840 SaveGamePGN(f)
10841      FILE *f;
10842 {
10843     int i, offset, linelen, newblock;
10844     time_t tm;
10845 //    char *movetext;
10846     char numtext[32];
10847     int movelen, numlen, blank;
10848     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10849
10850     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10851
10852     tm = time((time_t *) NULL);
10853
10854     PrintPGNTags(f, &gameInfo);
10855
10856     if (backwardMostMove > 0 || startedFromSetupPosition) {
10857         char *fen = PositionToFEN(backwardMostMove, NULL);
10858         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10859         fprintf(f, "\n{--------------\n");
10860         PrintPosition(f, backwardMostMove);
10861         fprintf(f, "--------------}\n");
10862         free(fen);
10863     }
10864     else {
10865         /* [AS] Out of book annotation */
10866         if( appData.saveOutOfBookInfo ) {
10867             char buf[64];
10868
10869             GetOutOfBookInfo( buf );
10870
10871             if( buf[0] != '\0' ) {
10872                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10873             }
10874         }
10875
10876         fprintf(f, "\n");
10877     }
10878
10879     i = backwardMostMove;
10880     linelen = 0;
10881     newblock = TRUE;
10882
10883     while (i < forwardMostMove) {
10884         /* Print comments preceding this move */
10885         if (commentList[i] != NULL) {
10886             if (linelen > 0) fprintf(f, "\n");
10887             fprintf(f, "%s", commentList[i]);
10888             linelen = 0;
10889             newblock = TRUE;
10890         }
10891
10892         /* Format move number */
10893         if ((i % 2) == 0)
10894           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10895         else
10896           if (newblock)
10897             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10898           else
10899             numtext[0] = NULLCHAR;
10900
10901         numlen = strlen(numtext);
10902         newblock = FALSE;
10903
10904         /* Print move number */
10905         blank = linelen > 0 && numlen > 0;
10906         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10907             fprintf(f, "\n");
10908             linelen = 0;
10909             blank = 0;
10910         }
10911         if (blank) {
10912             fprintf(f, " ");
10913             linelen++;
10914         }
10915         fprintf(f, "%s", numtext);
10916         linelen += numlen;
10917
10918         /* Get move */
10919         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10920         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10921
10922         /* Print move */
10923         blank = linelen > 0 && movelen > 0;
10924         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10925             fprintf(f, "\n");
10926             linelen = 0;
10927             blank = 0;
10928         }
10929         if (blank) {
10930             fprintf(f, " ");
10931             linelen++;
10932         }
10933         fprintf(f, "%s", move_buffer);
10934         linelen += movelen;
10935
10936         /* [AS] Add PV info if present */
10937         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10938             /* [HGM] add time */
10939             char buf[MSG_SIZ]; int seconds;
10940
10941             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10942
10943             if( seconds <= 0)
10944               buf[0] = 0;
10945             else
10946               if( seconds < 30 )
10947                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10948               else
10949                 {
10950                   seconds = (seconds + 4)/10; // round to full seconds
10951                   if( seconds < 60 )
10952                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10953                   else
10954                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10955                 }
10956
10957             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10958                       pvInfoList[i].score >= 0 ? "+" : "",
10959                       pvInfoList[i].score / 100.0,
10960                       pvInfoList[i].depth,
10961                       buf );
10962
10963             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10964
10965             /* Print score/depth */
10966             blank = linelen > 0 && movelen > 0;
10967             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10968                 fprintf(f, "\n");
10969                 linelen = 0;
10970                 blank = 0;
10971             }
10972             if (blank) {
10973                 fprintf(f, " ");
10974                 linelen++;
10975             }
10976             fprintf(f, "%s", move_buffer);
10977             linelen += movelen;
10978         }
10979
10980         i++;
10981     }
10982
10983     /* Start a new line */
10984     if (linelen > 0) fprintf(f, "\n");
10985
10986     /* Print comments after last move */
10987     if (commentList[i] != NULL) {
10988         fprintf(f, "%s\n", commentList[i]);
10989     }
10990
10991     /* Print result */
10992     if (gameInfo.resultDetails != NULL &&
10993         gameInfo.resultDetails[0] != NULLCHAR) {
10994         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10995                 PGNResult(gameInfo.result));
10996     } else {
10997         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10998     }
10999
11000     fclose(f);
11001     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11002     return TRUE;
11003 }
11004
11005 /* Save game in old style and close the file */
11006 int
11007 SaveGameOldStyle(f)
11008      FILE *f;
11009 {
11010     int i, offset;
11011     time_t tm;
11012
11013     tm = time((time_t *) NULL);
11014
11015     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11016     PrintOpponents(f);
11017
11018     if (backwardMostMove > 0 || startedFromSetupPosition) {
11019         fprintf(f, "\n[--------------\n");
11020         PrintPosition(f, backwardMostMove);
11021         fprintf(f, "--------------]\n");
11022     } else {
11023         fprintf(f, "\n");
11024     }
11025
11026     i = backwardMostMove;
11027     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11028
11029     while (i < forwardMostMove) {
11030         if (commentList[i] != NULL) {
11031             fprintf(f, "[%s]\n", commentList[i]);
11032         }
11033
11034         if ((i % 2) == 1) {
11035             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11036             i++;
11037         } else {
11038             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11039             i++;
11040             if (commentList[i] != NULL) {
11041                 fprintf(f, "\n");
11042                 continue;
11043             }
11044             if (i >= forwardMostMove) {
11045                 fprintf(f, "\n");
11046                 break;
11047             }
11048             fprintf(f, "%s\n", parseList[i]);
11049             i++;
11050         }
11051     }
11052
11053     if (commentList[i] != NULL) {
11054         fprintf(f, "[%s]\n", commentList[i]);
11055     }
11056
11057     /* This isn't really the old style, but it's close enough */
11058     if (gameInfo.resultDetails != NULL &&
11059         gameInfo.resultDetails[0] != NULLCHAR) {
11060         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11061                 gameInfo.resultDetails);
11062     } else {
11063         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11064     }
11065
11066     fclose(f);
11067     return TRUE;
11068 }
11069
11070 /* Save the current game to open file f and close the file */
11071 int
11072 SaveGame(f, dummy, dummy2)
11073      FILE *f;
11074      int dummy;
11075      char *dummy2;
11076 {
11077     if (gameMode == EditPosition) EditPositionDone(TRUE);
11078     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11079     if (appData.oldSaveStyle)
11080       return SaveGameOldStyle(f);
11081     else
11082       return SaveGamePGN(f);
11083 }
11084
11085 /* Save the current position to the given file */
11086 int
11087 SavePositionToFile(filename)
11088      char *filename;
11089 {
11090     FILE *f;
11091     char buf[MSG_SIZ];
11092
11093     if (strcmp(filename, "-") == 0) {
11094         return SavePosition(stdout, 0, NULL);
11095     } else {
11096         f = fopen(filename, "a");
11097         if (f == NULL) {
11098             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11099             DisplayError(buf, errno);
11100             return FALSE;
11101         } else {
11102             SavePosition(f, 0, NULL);
11103             return TRUE;
11104         }
11105     }
11106 }
11107
11108 /* Save the current position to the given open file and close the file */
11109 int
11110 SavePosition(f, dummy, dummy2)
11111      FILE *f;
11112      int dummy;
11113      char *dummy2;
11114 {
11115     time_t tm;
11116     char *fen;
11117
11118     if (gameMode == EditPosition) EditPositionDone(TRUE);
11119     if (appData.oldSaveStyle) {
11120         tm = time((time_t *) NULL);
11121
11122         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11123         PrintOpponents(f);
11124         fprintf(f, "[--------------\n");
11125         PrintPosition(f, currentMove);
11126         fprintf(f, "--------------]\n");
11127     } else {
11128         fen = PositionToFEN(currentMove, NULL);
11129         fprintf(f, "%s\n", fen);
11130         free(fen);
11131     }
11132     fclose(f);
11133     return TRUE;
11134 }
11135
11136 void
11137 ReloadCmailMsgEvent(unregister)
11138      int unregister;
11139 {
11140 #if !WIN32
11141     static char *inFilename = NULL;
11142     static char *outFilename;
11143     int i;
11144     struct stat inbuf, outbuf;
11145     int status;
11146
11147     /* Any registered moves are unregistered if unregister is set, */
11148     /* i.e. invoked by the signal handler */
11149     if (unregister) {
11150         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11151             cmailMoveRegistered[i] = FALSE;
11152             if (cmailCommentList[i] != NULL) {
11153                 free(cmailCommentList[i]);
11154                 cmailCommentList[i] = NULL;
11155             }
11156         }
11157         nCmailMovesRegistered = 0;
11158     }
11159
11160     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11161         cmailResult[i] = CMAIL_NOT_RESULT;
11162     }
11163     nCmailResults = 0;
11164
11165     if (inFilename == NULL) {
11166         /* Because the filenames are static they only get malloced once  */
11167         /* and they never get freed                                      */
11168         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11169         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11170
11171         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11172         sprintf(outFilename, "%s.out", appData.cmailGameName);
11173     }
11174
11175     status = stat(outFilename, &outbuf);
11176     if (status < 0) {
11177         cmailMailedMove = FALSE;
11178     } else {
11179         status = stat(inFilename, &inbuf);
11180         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11181     }
11182
11183     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11184        counts the games, notes how each one terminated, etc.
11185
11186        It would be nice to remove this kludge and instead gather all
11187        the information while building the game list.  (And to keep it
11188        in the game list nodes instead of having a bunch of fixed-size
11189        parallel arrays.)  Note this will require getting each game's
11190        termination from the PGN tags, as the game list builder does
11191        not process the game moves.  --mann
11192        */
11193     cmailMsgLoaded = TRUE;
11194     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11195
11196     /* Load first game in the file or popup game menu */
11197     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11198
11199 #endif /* !WIN32 */
11200     return;
11201 }
11202
11203 int
11204 RegisterMove()
11205 {
11206     FILE *f;
11207     char string[MSG_SIZ];
11208
11209     if (   cmailMailedMove
11210         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11211         return TRUE;            /* Allow free viewing  */
11212     }
11213
11214     /* Unregister move to ensure that we don't leave RegisterMove        */
11215     /* with the move registered when the conditions for registering no   */
11216     /* longer hold                                                       */
11217     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11218         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11219         nCmailMovesRegistered --;
11220
11221         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11222           {
11223               free(cmailCommentList[lastLoadGameNumber - 1]);
11224               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11225           }
11226     }
11227
11228     if (cmailOldMove == -1) {
11229         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11230         return FALSE;
11231     }
11232
11233     if (currentMove > cmailOldMove + 1) {
11234         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11235         return FALSE;
11236     }
11237
11238     if (currentMove < cmailOldMove) {
11239         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11240         return FALSE;
11241     }
11242
11243     if (forwardMostMove > currentMove) {
11244         /* Silently truncate extra moves */
11245         TruncateGame();
11246     }
11247
11248     if (   (currentMove == cmailOldMove + 1)
11249         || (   (currentMove == cmailOldMove)
11250             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11251                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11252         if (gameInfo.result != GameUnfinished) {
11253             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11254         }
11255
11256         if (commentList[currentMove] != NULL) {
11257             cmailCommentList[lastLoadGameNumber - 1]
11258               = StrSave(commentList[currentMove]);
11259         }
11260         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11261
11262         if (appData.debugMode)
11263           fprintf(debugFP, "Saving %s for game %d\n",
11264                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11265
11266         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11267
11268         f = fopen(string, "w");
11269         if (appData.oldSaveStyle) {
11270             SaveGameOldStyle(f); /* also closes the file */
11271
11272             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11273             f = fopen(string, "w");
11274             SavePosition(f, 0, NULL); /* also closes the file */
11275         } else {
11276             fprintf(f, "{--------------\n");
11277             PrintPosition(f, currentMove);
11278             fprintf(f, "--------------}\n\n");
11279
11280             SaveGame(f, 0, NULL); /* also closes the file*/
11281         }
11282
11283         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11284         nCmailMovesRegistered ++;
11285     } else if (nCmailGames == 1) {
11286         DisplayError(_("You have not made a move yet"), 0);
11287         return FALSE;
11288     }
11289
11290     return TRUE;
11291 }
11292
11293 void
11294 MailMoveEvent()
11295 {
11296 #if !WIN32
11297     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11298     FILE *commandOutput;
11299     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11300     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11301     int nBuffers;
11302     int i;
11303     int archived;
11304     char *arcDir;
11305
11306     if (! cmailMsgLoaded) {
11307         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11308         return;
11309     }
11310
11311     if (nCmailGames == nCmailResults) {
11312         DisplayError(_("No unfinished games"), 0);
11313         return;
11314     }
11315
11316 #if CMAIL_PROHIBIT_REMAIL
11317     if (cmailMailedMove) {
11318       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);
11319         DisplayError(msg, 0);
11320         return;
11321     }
11322 #endif
11323
11324     if (! (cmailMailedMove || RegisterMove())) return;
11325
11326     if (   cmailMailedMove
11327         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11328       snprintf(string, MSG_SIZ, partCommandString,
11329                appData.debugMode ? " -v" : "", appData.cmailGameName);
11330         commandOutput = popen(string, "r");
11331
11332         if (commandOutput == NULL) {
11333             DisplayError(_("Failed to invoke cmail"), 0);
11334         } else {
11335             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11336                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11337             }
11338             if (nBuffers > 1) {
11339                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11340                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11341                 nBytes = MSG_SIZ - 1;
11342             } else {
11343                 (void) memcpy(msg, buffer, nBytes);
11344             }
11345             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11346
11347             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11348                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11349
11350                 archived = TRUE;
11351                 for (i = 0; i < nCmailGames; i ++) {
11352                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11353                         archived = FALSE;
11354                     }
11355                 }
11356                 if (   archived
11357                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11358                         != NULL)) {
11359                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11360                            arcDir,
11361                            appData.cmailGameName,
11362                            gameInfo.date);
11363                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11364                     cmailMsgLoaded = FALSE;
11365                 }
11366             }
11367
11368             DisplayInformation(msg);
11369             pclose(commandOutput);
11370         }
11371     } else {
11372         if ((*cmailMsg) != '\0') {
11373             DisplayInformation(cmailMsg);
11374         }
11375     }
11376
11377     return;
11378 #endif /* !WIN32 */
11379 }
11380
11381 char *
11382 CmailMsg()
11383 {
11384 #if WIN32
11385     return NULL;
11386 #else
11387     int  prependComma = 0;
11388     char number[5];
11389     char string[MSG_SIZ];       /* Space for game-list */
11390     int  i;
11391
11392     if (!cmailMsgLoaded) return "";
11393
11394     if (cmailMailedMove) {
11395       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11396     } else {
11397         /* Create a list of games left */
11398       snprintf(string, MSG_SIZ, "[");
11399         for (i = 0; i < nCmailGames; i ++) {
11400             if (! (   cmailMoveRegistered[i]
11401                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11402                 if (prependComma) {
11403                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11404                 } else {
11405                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11406                     prependComma = 1;
11407                 }
11408
11409                 strcat(string, number);
11410             }
11411         }
11412         strcat(string, "]");
11413
11414         if (nCmailMovesRegistered + nCmailResults == 0) {
11415             switch (nCmailGames) {
11416               case 1:
11417                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11418                 break;
11419
11420               case 2:
11421                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11422                 break;
11423
11424               default:
11425                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11426                          nCmailGames);
11427                 break;
11428             }
11429         } else {
11430             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11431               case 1:
11432                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11433                          string);
11434                 break;
11435
11436               case 0:
11437                 if (nCmailResults == nCmailGames) {
11438                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11439                 } else {
11440                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11441                 }
11442                 break;
11443
11444               default:
11445                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11446                          string);
11447             }
11448         }
11449     }
11450     return cmailMsg;
11451 #endif /* WIN32 */
11452 }
11453
11454 void
11455 ResetGameEvent()
11456 {
11457     if (gameMode == Training)
11458       SetTrainingModeOff();
11459
11460     Reset(TRUE, TRUE);
11461     cmailMsgLoaded = FALSE;
11462     if (appData.icsActive) {
11463       SendToICS(ics_prefix);
11464       SendToICS("refresh\n");
11465     }
11466 }
11467
11468 void
11469 ExitEvent(status)
11470      int status;
11471 {
11472     exiting++;
11473     if (exiting > 2) {
11474       /* Give up on clean exit */
11475       exit(status);
11476     }
11477     if (exiting > 1) {
11478       /* Keep trying for clean exit */
11479       return;
11480     }
11481
11482     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11483
11484     if (telnetISR != NULL) {
11485       RemoveInputSource(telnetISR);
11486     }
11487     if (icsPR != NoProc) {
11488       DestroyChildProcess(icsPR, TRUE);
11489     }
11490
11491     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11492     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11493
11494     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11495     /* make sure this other one finishes before killing it!                  */
11496     if(endingGame) { int count = 0;
11497         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11498         while(endingGame && count++ < 10) DoSleep(1);
11499         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11500     }
11501
11502     /* Kill off chess programs */
11503     if (first.pr != NoProc) {
11504         ExitAnalyzeMode();
11505
11506         DoSleep( appData.delayBeforeQuit );
11507         SendToProgram("quit\n", &first);
11508         DoSleep( appData.delayAfterQuit );
11509         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11510     }
11511     if (second.pr != NoProc) {
11512         DoSleep( appData.delayBeforeQuit );
11513         SendToProgram("quit\n", &second);
11514         DoSleep( appData.delayAfterQuit );
11515         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11516     }
11517     if (first.isr != NULL) {
11518         RemoveInputSource(first.isr);
11519     }
11520     if (second.isr != NULL) {
11521         RemoveInputSource(second.isr);
11522     }
11523
11524     ShutDownFrontEnd();
11525     exit(status);
11526 }
11527
11528 void
11529 PauseEvent()
11530 {
11531     if (appData.debugMode)
11532         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11533     if (pausing) {
11534         pausing = FALSE;
11535         ModeHighlight();
11536         if (gameMode == MachinePlaysWhite ||
11537             gameMode == MachinePlaysBlack) {
11538             StartClocks();
11539         } else {
11540             DisplayBothClocks();
11541         }
11542         if (gameMode == PlayFromGameFile) {
11543             if (appData.timeDelay >= 0)
11544                 AutoPlayGameLoop();
11545         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11546             Reset(FALSE, TRUE);
11547             SendToICS(ics_prefix);
11548             SendToICS("refresh\n");
11549         } else if (currentMove < forwardMostMove) {
11550             ForwardInner(forwardMostMove);
11551         }
11552         pauseExamInvalid = FALSE;
11553     } else {
11554         switch (gameMode) {
11555           default:
11556             return;
11557           case IcsExamining:
11558             pauseExamForwardMostMove = forwardMostMove;
11559             pauseExamInvalid = FALSE;
11560             /* fall through */
11561           case IcsObserving:
11562           case IcsPlayingWhite:
11563           case IcsPlayingBlack:
11564             pausing = TRUE;
11565             ModeHighlight();
11566             return;
11567           case PlayFromGameFile:
11568             (void) StopLoadGameTimer();
11569             pausing = TRUE;
11570             ModeHighlight();
11571             break;
11572           case BeginningOfGame:
11573             if (appData.icsActive) return;
11574             /* else fall through */
11575           case MachinePlaysWhite:
11576           case MachinePlaysBlack:
11577           case TwoMachinesPlay:
11578             if (forwardMostMove == 0)
11579               return;           /* don't pause if no one has moved */
11580             if ((gameMode == MachinePlaysWhite &&
11581                  !WhiteOnMove(forwardMostMove)) ||
11582                 (gameMode == MachinePlaysBlack &&
11583                  WhiteOnMove(forwardMostMove))) {
11584                 StopClocks();
11585             }
11586             pausing = TRUE;
11587             ModeHighlight();
11588             break;
11589         }
11590     }
11591 }
11592
11593 void
11594 EditCommentEvent()
11595 {
11596     char title[MSG_SIZ];
11597
11598     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11599       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11600     } else {
11601       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11602                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11603                parseList[currentMove - 1]);
11604     }
11605
11606     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11607 }
11608
11609
11610 void
11611 EditTagsEvent()
11612 {
11613     char *tags = PGNTags(&gameInfo);
11614     EditTagsPopUp(tags);
11615     free(tags);
11616 }
11617
11618 void
11619 AnalyzeModeEvent()
11620 {
11621     if (appData.noChessProgram || gameMode == AnalyzeMode)
11622       return;
11623
11624     if (gameMode != AnalyzeFile) {
11625         if (!appData.icsEngineAnalyze) {
11626                EditGameEvent();
11627                if (gameMode != EditGame) return;
11628         }
11629         ResurrectChessProgram();
11630         SendToProgram("analyze\n", &first);
11631         first.analyzing = TRUE;
11632         /*first.maybeThinking = TRUE;*/
11633         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11634         EngineOutputPopUp();
11635     }
11636     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11637     pausing = FALSE;
11638     ModeHighlight();
11639     SetGameInfo();
11640
11641     StartAnalysisClock();
11642     GetTimeMark(&lastNodeCountTime);
11643     lastNodeCount = 0;
11644 }
11645
11646 void
11647 AnalyzeFileEvent()
11648 {
11649     if (appData.noChessProgram || gameMode == AnalyzeFile)
11650       return;
11651
11652     if (gameMode != AnalyzeMode) {
11653         EditGameEvent();
11654         if (gameMode != EditGame) return;
11655         ResurrectChessProgram();
11656         SendToProgram("analyze\n", &first);
11657         first.analyzing = TRUE;
11658         /*first.maybeThinking = TRUE;*/
11659         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11660         EngineOutputPopUp();
11661     }
11662     gameMode = AnalyzeFile;
11663     pausing = FALSE;
11664     ModeHighlight();
11665     SetGameInfo();
11666
11667     StartAnalysisClock();
11668     GetTimeMark(&lastNodeCountTime);
11669     lastNodeCount = 0;
11670 }
11671
11672 void
11673 MachineWhiteEvent()
11674 {
11675     char buf[MSG_SIZ];
11676     char *bookHit = NULL;
11677
11678     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11679       return;
11680
11681
11682     if (gameMode == PlayFromGameFile ||
11683         gameMode == TwoMachinesPlay  ||
11684         gameMode == Training         ||
11685         gameMode == AnalyzeMode      ||
11686         gameMode == EndOfGame)
11687         EditGameEvent();
11688
11689     if (gameMode == EditPosition)
11690         EditPositionDone(TRUE);
11691
11692     if (!WhiteOnMove(currentMove)) {
11693         DisplayError(_("It is not White's turn"), 0);
11694         return;
11695     }
11696
11697     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11698       ExitAnalyzeMode();
11699
11700     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11701         gameMode == AnalyzeFile)
11702         TruncateGame();
11703
11704     ResurrectChessProgram();    /* in case it isn't running */
11705     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11706         gameMode = MachinePlaysWhite;
11707         ResetClocks();
11708     } else
11709     gameMode = MachinePlaysWhite;
11710     pausing = FALSE;
11711     ModeHighlight();
11712     SetGameInfo();
11713     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11714     DisplayTitle(buf);
11715     if (first.sendName) {
11716       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11717       SendToProgram(buf, &first);
11718     }
11719     if (first.sendTime) {
11720       if (first.useColors) {
11721         SendToProgram("black\n", &first); /*gnu kludge*/
11722       }
11723       SendTimeRemaining(&first, TRUE);
11724     }
11725     if (first.useColors) {
11726       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11727     }
11728     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11729     SetMachineThinkingEnables();
11730     first.maybeThinking = TRUE;
11731     StartClocks();
11732     firstMove = FALSE;
11733
11734     if (appData.autoFlipView && !flipView) {
11735       flipView = !flipView;
11736       DrawPosition(FALSE, NULL);
11737       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11738     }
11739
11740     if(bookHit) { // [HGM] book: simulate book reply
11741         static char bookMove[MSG_SIZ]; // a bit generous?
11742
11743         programStats.nodes = programStats.depth = programStats.time =
11744         programStats.score = programStats.got_only_move = 0;
11745         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11746
11747         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11748         strcat(bookMove, bookHit);
11749         HandleMachineMove(bookMove, &first);
11750     }
11751 }
11752
11753 void
11754 MachineBlackEvent()
11755 {
11756   char buf[MSG_SIZ];
11757   char *bookHit = NULL;
11758
11759     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11760         return;
11761
11762
11763     if (gameMode == PlayFromGameFile ||
11764         gameMode == TwoMachinesPlay  ||
11765         gameMode == Training         ||
11766         gameMode == AnalyzeMode      ||
11767         gameMode == EndOfGame)
11768         EditGameEvent();
11769
11770     if (gameMode == EditPosition)
11771         EditPositionDone(TRUE);
11772
11773     if (WhiteOnMove(currentMove)) {
11774         DisplayError(_("It is not Black's turn"), 0);
11775         return;
11776     }
11777
11778     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11779       ExitAnalyzeMode();
11780
11781     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11782         gameMode == AnalyzeFile)
11783         TruncateGame();
11784
11785     ResurrectChessProgram();    /* in case it isn't running */
11786     gameMode = MachinePlaysBlack;
11787     pausing = FALSE;
11788     ModeHighlight();
11789     SetGameInfo();
11790     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11791     DisplayTitle(buf);
11792     if (first.sendName) {
11793       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11794       SendToProgram(buf, &first);
11795     }
11796     if (first.sendTime) {
11797       if (first.useColors) {
11798         SendToProgram("white\n", &first); /*gnu kludge*/
11799       }
11800       SendTimeRemaining(&first, FALSE);
11801     }
11802     if (first.useColors) {
11803       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11804     }
11805     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11806     SetMachineThinkingEnables();
11807     first.maybeThinking = TRUE;
11808     StartClocks();
11809
11810     if (appData.autoFlipView && flipView) {
11811       flipView = !flipView;
11812       DrawPosition(FALSE, NULL);
11813       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11814     }
11815     if(bookHit) { // [HGM] book: simulate book reply
11816         static char bookMove[MSG_SIZ]; // a bit generous?
11817
11818         programStats.nodes = programStats.depth = programStats.time =
11819         programStats.score = programStats.got_only_move = 0;
11820         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11821
11822         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11823         strcat(bookMove, bookHit);
11824         HandleMachineMove(bookMove, &first);
11825     }
11826 }
11827
11828
11829 void
11830 DisplayTwoMachinesTitle()
11831 {
11832     char buf[MSG_SIZ];
11833     if (appData.matchGames > 0) {
11834         if (first.twoMachinesColor[0] == 'w') {
11835           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11836                    gameInfo.white, gameInfo.black,
11837                    first.matchWins, second.matchWins,
11838                    matchGame - 1 - (first.matchWins + second.matchWins));
11839         } else {
11840           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11841                    gameInfo.white, gameInfo.black,
11842                    second.matchWins, first.matchWins,
11843                    matchGame - 1 - (first.matchWins + second.matchWins));
11844         }
11845     } else {
11846       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11847     }
11848     DisplayTitle(buf);
11849 }
11850
11851 void
11852 SettingsMenuIfReady()
11853 {
11854   if (second.lastPing != second.lastPong) {
11855     DisplayMessage("", _("Waiting for second chess program"));
11856     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11857     return;
11858   }
11859   ThawUI();
11860   DisplayMessage("", "");
11861   SettingsPopUp(&second);
11862 }
11863
11864 int
11865 WaitForSecond(DelayedEventCallback retry)
11866 {
11867     if (second.pr == NULL) {
11868         StartChessProgram(&second);
11869         if (second.protocolVersion == 1) {
11870           retry();
11871         } else {
11872           /* kludge: allow timeout for initial "feature" command */
11873           FreezeUI();
11874           DisplayMessage("", _("Starting second chess program"));
11875           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11876         }
11877         return 1;
11878     }
11879     return 0;
11880 }
11881
11882 void
11883 TwoMachinesEvent P((void))
11884 {
11885     int i;
11886     char buf[MSG_SIZ];
11887     ChessProgramState *onmove;
11888     char *bookHit = NULL;
11889
11890     if (appData.noChessProgram) return;
11891
11892     switch (gameMode) {
11893       case TwoMachinesPlay:
11894         return;
11895       case MachinePlaysWhite:
11896       case MachinePlaysBlack:
11897         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11898             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11899             return;
11900         }
11901         /* fall through */
11902       case BeginningOfGame:
11903       case PlayFromGameFile:
11904       case EndOfGame:
11905         EditGameEvent();
11906         if (gameMode != EditGame) return;
11907         break;
11908       case EditPosition:
11909         EditPositionDone(TRUE);
11910         break;
11911       case AnalyzeMode:
11912       case AnalyzeFile:
11913         ExitAnalyzeMode();
11914         break;
11915       case EditGame:
11916       default:
11917         break;
11918     }
11919
11920 //    forwardMostMove = currentMove;
11921     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11922     ResurrectChessProgram();    /* in case first program isn't running */
11923
11924     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11925     DisplayMessage("", "");
11926     InitChessProgram(&second, FALSE);
11927     SendToProgram("force\n", &second);
11928     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11929       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11930       return;
11931     }
11932     if (startedFromSetupPosition) {
11933         SendBoard(&second, backwardMostMove);
11934     if (appData.debugMode) {
11935         fprintf(debugFP, "Two Machines\n");
11936     }
11937     }
11938     for (i = backwardMostMove; i < forwardMostMove; i++) {
11939         SendMoveToProgram(i, &second);
11940     }
11941
11942     gameMode = TwoMachinesPlay;
11943     pausing = FALSE;
11944     ModeHighlight();
11945     SetGameInfo();
11946     DisplayTwoMachinesTitle();
11947     firstMove = TRUE;
11948     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11949         onmove = &first;
11950     } else {
11951         onmove = &second;
11952     }
11953
11954     SendToProgram(first.computerString, &first);
11955     if (first.sendName) {
11956       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11957       SendToProgram(buf, &first);
11958     }
11959     SendToProgram(second.computerString, &second);
11960     if (second.sendName) {
11961       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11962       SendToProgram(buf, &second);
11963     }
11964
11965     ResetClocks();
11966     if (!first.sendTime || !second.sendTime) {
11967         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11968         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11969     }
11970     if (onmove->sendTime) {
11971       if (onmove->useColors) {
11972         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11973       }
11974       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11975     }
11976     if (onmove->useColors) {
11977       SendToProgram(onmove->twoMachinesColor, onmove);
11978     }
11979     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11980 //    SendToProgram("go\n", onmove);
11981     onmove->maybeThinking = TRUE;
11982     SetMachineThinkingEnables();
11983
11984     StartClocks();
11985
11986     if(bookHit) { // [HGM] book: simulate book reply
11987         static char bookMove[MSG_SIZ]; // a bit generous?
11988
11989         programStats.nodes = programStats.depth = programStats.time =
11990         programStats.score = programStats.got_only_move = 0;
11991         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11992
11993         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11994         strcat(bookMove, bookHit);
11995         savedMessage = bookMove; // args for deferred call
11996         savedState = onmove;
11997         ScheduleDelayedEvent(DeferredBookMove, 1);
11998     }
11999 }
12000
12001 void
12002 TrainingEvent()
12003 {
12004     if (gameMode == Training) {
12005       SetTrainingModeOff();
12006       gameMode = PlayFromGameFile;
12007       DisplayMessage("", _("Training mode off"));
12008     } else {
12009       gameMode = Training;
12010       animateTraining = appData.animate;
12011
12012       /* make sure we are not already at the end of the game */
12013       if (currentMove < forwardMostMove) {
12014         SetTrainingModeOn();
12015         DisplayMessage("", _("Training mode on"));
12016       } else {
12017         gameMode = PlayFromGameFile;
12018         DisplayError(_("Already at end of game"), 0);
12019       }
12020     }
12021     ModeHighlight();
12022 }
12023
12024 void
12025 IcsClientEvent()
12026 {
12027     if (!appData.icsActive) return;
12028     switch (gameMode) {
12029       case IcsPlayingWhite:
12030       case IcsPlayingBlack:
12031       case IcsObserving:
12032       case IcsIdle:
12033       case BeginningOfGame:
12034       case IcsExamining:
12035         return;
12036
12037       case EditGame:
12038         break;
12039
12040       case EditPosition:
12041         EditPositionDone(TRUE);
12042         break;
12043
12044       case AnalyzeMode:
12045       case AnalyzeFile:
12046         ExitAnalyzeMode();
12047         break;
12048
12049       default:
12050         EditGameEvent();
12051         break;
12052     }
12053
12054     gameMode = IcsIdle;
12055     ModeHighlight();
12056     return;
12057 }
12058
12059
12060 void
12061 EditGameEvent()
12062 {
12063     int i;
12064
12065     switch (gameMode) {
12066       case Training:
12067         SetTrainingModeOff();
12068         break;
12069       case MachinePlaysWhite:
12070       case MachinePlaysBlack:
12071       case BeginningOfGame:
12072         SendToProgram("force\n", &first);
12073         SetUserThinkingEnables();
12074         break;
12075       case PlayFromGameFile:
12076         (void) StopLoadGameTimer();
12077         if (gameFileFP != NULL) {
12078             gameFileFP = NULL;
12079         }
12080         break;
12081       case EditPosition:
12082         EditPositionDone(TRUE);
12083         break;
12084       case AnalyzeMode:
12085       case AnalyzeFile:
12086         ExitAnalyzeMode();
12087         SendToProgram("force\n", &first);
12088         break;
12089       case TwoMachinesPlay:
12090         GameEnds(EndOfFile, NULL, GE_PLAYER);
12091         ResurrectChessProgram();
12092         SetUserThinkingEnables();
12093         break;
12094       case EndOfGame:
12095         ResurrectChessProgram();
12096         break;
12097       case IcsPlayingBlack:
12098       case IcsPlayingWhite:
12099         DisplayError(_("Warning: You are still playing a game"), 0);
12100         break;
12101       case IcsObserving:
12102         DisplayError(_("Warning: You are still observing a game"), 0);
12103         break;
12104       case IcsExamining:
12105         DisplayError(_("Warning: You are still examining a game"), 0);
12106         break;
12107       case IcsIdle:
12108         break;
12109       case EditGame:
12110       default:
12111         return;
12112     }
12113
12114     pausing = FALSE;
12115     StopClocks();
12116     first.offeredDraw = second.offeredDraw = 0;
12117
12118     if (gameMode == PlayFromGameFile) {
12119         whiteTimeRemaining = timeRemaining[0][currentMove];
12120         blackTimeRemaining = timeRemaining[1][currentMove];
12121         DisplayTitle("");
12122     }
12123
12124     if (gameMode == MachinePlaysWhite ||
12125         gameMode == MachinePlaysBlack ||
12126         gameMode == TwoMachinesPlay ||
12127         gameMode == EndOfGame) {
12128         i = forwardMostMove;
12129         while (i > currentMove) {
12130             SendToProgram("undo\n", &first);
12131             i--;
12132         }
12133         whiteTimeRemaining = timeRemaining[0][currentMove];
12134         blackTimeRemaining = timeRemaining[1][currentMove];
12135         DisplayBothClocks();
12136         if (whiteFlag || blackFlag) {
12137             whiteFlag = blackFlag = 0;
12138         }
12139         DisplayTitle("");
12140     }
12141
12142     gameMode = EditGame;
12143     ModeHighlight();
12144     SetGameInfo();
12145 }
12146
12147
12148 void
12149 EditPositionEvent()
12150 {
12151     if (gameMode == EditPosition) {
12152         EditGameEvent();
12153         return;
12154     }
12155
12156     EditGameEvent();
12157     if (gameMode != EditGame) return;
12158
12159     gameMode = EditPosition;
12160     ModeHighlight();
12161     SetGameInfo();
12162     if (currentMove > 0)
12163       CopyBoard(boards[0], boards[currentMove]);
12164
12165     blackPlaysFirst = !WhiteOnMove(currentMove);
12166     ResetClocks();
12167     currentMove = forwardMostMove = backwardMostMove = 0;
12168     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12169     DisplayMove(-1);
12170 }
12171
12172 void
12173 ExitAnalyzeMode()
12174 {
12175     /* [DM] icsEngineAnalyze - possible call from other functions */
12176     if (appData.icsEngineAnalyze) {
12177         appData.icsEngineAnalyze = FALSE;
12178
12179         DisplayMessage("",_("Close ICS engine analyze..."));
12180     }
12181     if (first.analysisSupport && first.analyzing) {
12182       SendToProgram("exit\n", &first);
12183       first.analyzing = FALSE;
12184     }
12185     thinkOutput[0] = NULLCHAR;
12186 }
12187
12188 void
12189 EditPositionDone(Boolean fakeRights)
12190 {
12191     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12192
12193     startedFromSetupPosition = TRUE;
12194     InitChessProgram(&first, FALSE);
12195     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12196       boards[0][EP_STATUS] = EP_NONE;
12197       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12198     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12199         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12200         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12201       } else boards[0][CASTLING][2] = NoRights;
12202     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12203         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12204         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12205       } else boards[0][CASTLING][5] = NoRights;
12206     }
12207     SendToProgram("force\n", &first);
12208     if (blackPlaysFirst) {
12209         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12210         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12211         currentMove = forwardMostMove = backwardMostMove = 1;
12212         CopyBoard(boards[1], boards[0]);
12213     } else {
12214         currentMove = forwardMostMove = backwardMostMove = 0;
12215     }
12216     SendBoard(&first, forwardMostMove);
12217     if (appData.debugMode) {
12218         fprintf(debugFP, "EditPosDone\n");
12219     }
12220     DisplayTitle("");
12221     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12222     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12223     gameMode = EditGame;
12224     ModeHighlight();
12225     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12226     ClearHighlights(); /* [AS] */
12227 }
12228
12229 /* Pause for `ms' milliseconds */
12230 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12231 void
12232 TimeDelay(ms)
12233      long ms;
12234 {
12235     TimeMark m1, m2;
12236
12237     GetTimeMark(&m1);
12238     do {
12239         GetTimeMark(&m2);
12240     } while (SubtractTimeMarks(&m2, &m1) < ms);
12241 }
12242
12243 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12244 void
12245 SendMultiLineToICS(buf)
12246      char *buf;
12247 {
12248     char temp[MSG_SIZ+1], *p;
12249     int len;
12250
12251     len = strlen(buf);
12252     if (len > MSG_SIZ)
12253       len = MSG_SIZ;
12254
12255     strncpy(temp, buf, len);
12256     temp[len] = 0;
12257
12258     p = temp;
12259     while (*p) {
12260         if (*p == '\n' || *p == '\r')
12261           *p = ' ';
12262         ++p;
12263     }
12264
12265     strcat(temp, "\n");
12266     SendToICS(temp);
12267     SendToPlayer(temp, strlen(temp));
12268 }
12269
12270 void
12271 SetWhiteToPlayEvent()
12272 {
12273     if (gameMode == EditPosition) {
12274         blackPlaysFirst = FALSE;
12275         DisplayBothClocks();    /* works because currentMove is 0 */
12276     } else if (gameMode == IcsExamining) {
12277         SendToICS(ics_prefix);
12278         SendToICS("tomove white\n");
12279     }
12280 }
12281
12282 void
12283 SetBlackToPlayEvent()
12284 {
12285     if (gameMode == EditPosition) {
12286         blackPlaysFirst = TRUE;
12287         currentMove = 1;        /* kludge */
12288         DisplayBothClocks();
12289         currentMove = 0;
12290     } else if (gameMode == IcsExamining) {
12291         SendToICS(ics_prefix);
12292         SendToICS("tomove black\n");
12293     }
12294 }
12295
12296 void
12297 EditPositionMenuEvent(selection, x, y)
12298      ChessSquare selection;
12299      int x, y;
12300 {
12301     char buf[MSG_SIZ];
12302     ChessSquare piece = boards[0][y][x];
12303
12304     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12305
12306     switch (selection) {
12307       case ClearBoard:
12308         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12309             SendToICS(ics_prefix);
12310             SendToICS("bsetup clear\n");
12311         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12312             SendToICS(ics_prefix);
12313             SendToICS("clearboard\n");
12314         } else {
12315             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12316                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12317                 for (y = 0; y < BOARD_HEIGHT; y++) {
12318                     if (gameMode == IcsExamining) {
12319                         if (boards[currentMove][y][x] != EmptySquare) {
12320                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12321                                     AAA + x, ONE + y);
12322                             SendToICS(buf);
12323                         }
12324                     } else {
12325                         boards[0][y][x] = p;
12326                     }
12327                 }
12328             }
12329         }
12330         if (gameMode == EditPosition) {
12331             DrawPosition(FALSE, boards[0]);
12332         }
12333         break;
12334
12335       case WhitePlay:
12336         SetWhiteToPlayEvent();
12337         break;
12338
12339       case BlackPlay:
12340         SetBlackToPlayEvent();
12341         break;
12342
12343       case EmptySquare:
12344         if (gameMode == IcsExamining) {
12345             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12346             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12347             SendToICS(buf);
12348         } else {
12349             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12350                 if(x == BOARD_LEFT-2) {
12351                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12352                     boards[0][y][1] = 0;
12353                 } else
12354                 if(x == BOARD_RGHT+1) {
12355                     if(y >= gameInfo.holdingsSize) break;
12356                     boards[0][y][BOARD_WIDTH-2] = 0;
12357                 } else break;
12358             }
12359             boards[0][y][x] = EmptySquare;
12360             DrawPosition(FALSE, boards[0]);
12361         }
12362         break;
12363
12364       case PromotePiece:
12365         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12366            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12367             selection = (ChessSquare) (PROMOTED piece);
12368         } else if(piece == EmptySquare) selection = WhiteSilver;
12369         else selection = (ChessSquare)((int)piece - 1);
12370         goto defaultlabel;
12371
12372       case DemotePiece:
12373         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12374            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12375             selection = (ChessSquare) (DEMOTED piece);
12376         } else if(piece == EmptySquare) selection = BlackSilver;
12377         else selection = (ChessSquare)((int)piece + 1);
12378         goto defaultlabel;
12379
12380       case WhiteQueen:
12381       case BlackQueen:
12382         if(gameInfo.variant == VariantShatranj ||
12383            gameInfo.variant == VariantXiangqi  ||
12384            gameInfo.variant == VariantCourier  ||
12385            gameInfo.variant == VariantMakruk     )
12386             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12387         goto defaultlabel;
12388
12389       case WhiteKing:
12390       case BlackKing:
12391         if(gameInfo.variant == VariantXiangqi)
12392             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12393         if(gameInfo.variant == VariantKnightmate)
12394             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12395       default:
12396         defaultlabel:
12397         if (gameMode == IcsExamining) {
12398             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12399             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12400                      PieceToChar(selection), AAA + x, ONE + y);
12401             SendToICS(buf);
12402         } else {
12403             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12404                 int n;
12405                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12406                     n = PieceToNumber(selection - BlackPawn);
12407                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12408                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12409                     boards[0][BOARD_HEIGHT-1-n][1]++;
12410                 } else
12411                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12412                     n = PieceToNumber(selection);
12413                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12414                     boards[0][n][BOARD_WIDTH-1] = selection;
12415                     boards[0][n][BOARD_WIDTH-2]++;
12416                 }
12417             } else
12418             boards[0][y][x] = selection;
12419             DrawPosition(TRUE, boards[0]);
12420         }
12421         break;
12422     }
12423 }
12424
12425
12426 void
12427 DropMenuEvent(selection, x, y)
12428      ChessSquare selection;
12429      int x, y;
12430 {
12431     ChessMove moveType;
12432
12433     switch (gameMode) {
12434       case IcsPlayingWhite:
12435       case MachinePlaysBlack:
12436         if (!WhiteOnMove(currentMove)) {
12437             DisplayMoveError(_("It is Black's turn"));
12438             return;
12439         }
12440         moveType = WhiteDrop;
12441         break;
12442       case IcsPlayingBlack:
12443       case MachinePlaysWhite:
12444         if (WhiteOnMove(currentMove)) {
12445             DisplayMoveError(_("It is White's turn"));
12446             return;
12447         }
12448         moveType = BlackDrop;
12449         break;
12450       case EditGame:
12451         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12452         break;
12453       default:
12454         return;
12455     }
12456
12457     if (moveType == BlackDrop && selection < BlackPawn) {
12458       selection = (ChessSquare) ((int) selection
12459                                  + (int) BlackPawn - (int) WhitePawn);
12460     }
12461     if (boards[currentMove][y][x] != EmptySquare) {
12462         DisplayMoveError(_("That square is occupied"));
12463         return;
12464     }
12465
12466     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12467 }
12468
12469 void
12470 AcceptEvent()
12471 {
12472     /* Accept a pending offer of any kind from opponent */
12473
12474     if (appData.icsActive) {
12475         SendToICS(ics_prefix);
12476         SendToICS("accept\n");
12477     } else if (cmailMsgLoaded) {
12478         if (currentMove == cmailOldMove &&
12479             commentList[cmailOldMove] != NULL &&
12480             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12481                    "Black offers a draw" : "White offers a draw")) {
12482             TruncateGame();
12483             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12484             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12485         } else {
12486             DisplayError(_("There is no pending offer on this move"), 0);
12487             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12488         }
12489     } else {
12490         /* Not used for offers from chess program */
12491     }
12492 }
12493
12494 void
12495 DeclineEvent()
12496 {
12497     /* Decline a pending offer of any kind from opponent */
12498
12499     if (appData.icsActive) {
12500         SendToICS(ics_prefix);
12501         SendToICS("decline\n");
12502     } else if (cmailMsgLoaded) {
12503         if (currentMove == cmailOldMove &&
12504             commentList[cmailOldMove] != NULL &&
12505             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12506                    "Black offers a draw" : "White offers a draw")) {
12507 #ifdef NOTDEF
12508             AppendComment(cmailOldMove, "Draw declined", TRUE);
12509             DisplayComment(cmailOldMove - 1, "Draw declined");
12510 #endif /*NOTDEF*/
12511         } else {
12512             DisplayError(_("There is no pending offer on this move"), 0);
12513         }
12514     } else {
12515         /* Not used for offers from chess program */
12516     }
12517 }
12518
12519 void
12520 RematchEvent()
12521 {
12522     /* Issue ICS rematch command */
12523     if (appData.icsActive) {
12524         SendToICS(ics_prefix);
12525         SendToICS("rematch\n");
12526     }
12527 }
12528
12529 void
12530 CallFlagEvent()
12531 {
12532     /* Call your opponent's flag (claim a win on time) */
12533     if (appData.icsActive) {
12534         SendToICS(ics_prefix);
12535         SendToICS("flag\n");
12536     } else {
12537         switch (gameMode) {
12538           default:
12539             return;
12540           case MachinePlaysWhite:
12541             if (whiteFlag) {
12542                 if (blackFlag)
12543                   GameEnds(GameIsDrawn, "Both players ran out of time",
12544                            GE_PLAYER);
12545                 else
12546                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12547             } else {
12548                 DisplayError(_("Your opponent is not out of time"), 0);
12549             }
12550             break;
12551           case MachinePlaysBlack:
12552             if (blackFlag) {
12553                 if (whiteFlag)
12554                   GameEnds(GameIsDrawn, "Both players ran out of time",
12555                            GE_PLAYER);
12556                 else
12557                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12558             } else {
12559                 DisplayError(_("Your opponent is not out of time"), 0);
12560             }
12561             break;
12562         }
12563     }
12564 }
12565
12566 void
12567 DrawEvent()
12568 {
12569     /* Offer draw or accept pending draw offer from opponent */
12570
12571     if (appData.icsActive) {
12572         /* Note: tournament rules require draw offers to be
12573            made after you make your move but before you punch
12574            your clock.  Currently ICS doesn't let you do that;
12575            instead, you immediately punch your clock after making
12576            a move, but you can offer a draw at any time. */
12577
12578         SendToICS(ics_prefix);
12579         SendToICS("draw\n");
12580         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12581     } else if (cmailMsgLoaded) {
12582         if (currentMove == cmailOldMove &&
12583             commentList[cmailOldMove] != NULL &&
12584             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12585                    "Black offers a draw" : "White offers a draw")) {
12586             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12587             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12588         } else if (currentMove == cmailOldMove + 1) {
12589             char *offer = WhiteOnMove(cmailOldMove) ?
12590               "White offers a draw" : "Black offers a draw";
12591             AppendComment(currentMove, offer, TRUE);
12592             DisplayComment(currentMove - 1, offer);
12593             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12594         } else {
12595             DisplayError(_("You must make your move before offering a draw"), 0);
12596             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12597         }
12598     } else if (first.offeredDraw) {
12599         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12600     } else {
12601         if (first.sendDrawOffers) {
12602             SendToProgram("draw\n", &first);
12603             userOfferedDraw = TRUE;
12604         }
12605     }
12606 }
12607
12608 void
12609 AdjournEvent()
12610 {
12611     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12612
12613     if (appData.icsActive) {
12614         SendToICS(ics_prefix);
12615         SendToICS("adjourn\n");
12616     } else {
12617         /* Currently GNU Chess doesn't offer or accept Adjourns */
12618     }
12619 }
12620
12621
12622 void
12623 AbortEvent()
12624 {
12625     /* Offer Abort or accept pending Abort offer from opponent */
12626
12627     if (appData.icsActive) {
12628         SendToICS(ics_prefix);
12629         SendToICS("abort\n");
12630     } else {
12631         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12632     }
12633 }
12634
12635 void
12636 ResignEvent()
12637 {
12638     /* Resign.  You can do this even if it's not your turn. */
12639
12640     if (appData.icsActive) {
12641         SendToICS(ics_prefix);
12642         SendToICS("resign\n");
12643     } else {
12644         switch (gameMode) {
12645           case MachinePlaysWhite:
12646             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12647             break;
12648           case MachinePlaysBlack:
12649             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12650             break;
12651           case EditGame:
12652             if (cmailMsgLoaded) {
12653                 TruncateGame();
12654                 if (WhiteOnMove(cmailOldMove)) {
12655                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12656                 } else {
12657                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12658                 }
12659                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12660             }
12661             break;
12662           default:
12663             break;
12664         }
12665     }
12666 }
12667
12668
12669 void
12670 StopObservingEvent()
12671 {
12672     /* Stop observing current games */
12673     SendToICS(ics_prefix);
12674     SendToICS("unobserve\n");
12675 }
12676
12677 void
12678 StopExaminingEvent()
12679 {
12680     /* Stop observing current game */
12681     SendToICS(ics_prefix);
12682     SendToICS("unexamine\n");
12683 }
12684
12685 void
12686 ForwardInner(target)
12687      int target;
12688 {
12689     int limit;
12690
12691     if (appData.debugMode)
12692         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12693                 target, currentMove, forwardMostMove);
12694
12695     if (gameMode == EditPosition)
12696       return;
12697
12698     if (gameMode == PlayFromGameFile && !pausing)
12699       PauseEvent();
12700
12701     if (gameMode == IcsExamining && pausing)
12702       limit = pauseExamForwardMostMove;
12703     else
12704       limit = forwardMostMove;
12705
12706     if (target > limit) target = limit;
12707
12708     if (target > 0 && moveList[target - 1][0]) {
12709         int fromX, fromY, toX, toY;
12710         toX = moveList[target - 1][2] - AAA;
12711         toY = moveList[target - 1][3] - ONE;
12712         if (moveList[target - 1][1] == '@') {
12713             if (appData.highlightLastMove) {
12714                 SetHighlights(-1, -1, toX, toY);
12715             }
12716         } else {
12717             fromX = moveList[target - 1][0] - AAA;
12718             fromY = moveList[target - 1][1] - ONE;
12719             if (target == currentMove + 1) {
12720                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12721             }
12722             if (appData.highlightLastMove) {
12723                 SetHighlights(fromX, fromY, toX, toY);
12724             }
12725         }
12726     }
12727     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12728         gameMode == Training || gameMode == PlayFromGameFile ||
12729         gameMode == AnalyzeFile) {
12730         while (currentMove < target) {
12731             SendMoveToProgram(currentMove++, &first);
12732         }
12733     } else {
12734         currentMove = target;
12735     }
12736
12737     if (gameMode == EditGame || gameMode == EndOfGame) {
12738         whiteTimeRemaining = timeRemaining[0][currentMove];
12739         blackTimeRemaining = timeRemaining[1][currentMove];
12740     }
12741     DisplayBothClocks();
12742     DisplayMove(currentMove - 1);
12743     DrawPosition(FALSE, boards[currentMove]);
12744     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12745     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12746         DisplayComment(currentMove - 1, commentList[currentMove]);
12747     }
12748 }
12749
12750
12751 void
12752 ForwardEvent()
12753 {
12754     if (gameMode == IcsExamining && !pausing) {
12755         SendToICS(ics_prefix);
12756         SendToICS("forward\n");
12757     } else {
12758         ForwardInner(currentMove + 1);
12759     }
12760 }
12761
12762 void
12763 ToEndEvent()
12764 {
12765     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12766         /* to optimze, we temporarily turn off analysis mode while we feed
12767          * the remaining moves to the engine. Otherwise we get analysis output
12768          * after each move.
12769          */
12770         if (first.analysisSupport) {
12771           SendToProgram("exit\nforce\n", &first);
12772           first.analyzing = FALSE;
12773         }
12774     }
12775
12776     if (gameMode == IcsExamining && !pausing) {
12777         SendToICS(ics_prefix);
12778         SendToICS("forward 999999\n");
12779     } else {
12780         ForwardInner(forwardMostMove);
12781     }
12782
12783     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12784         /* we have fed all the moves, so reactivate analysis mode */
12785         SendToProgram("analyze\n", &first);
12786         first.analyzing = TRUE;
12787         /*first.maybeThinking = TRUE;*/
12788         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12789     }
12790 }
12791
12792 void
12793 BackwardInner(target)
12794      int target;
12795 {
12796     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12797
12798     if (appData.debugMode)
12799         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12800                 target, currentMove, forwardMostMove);
12801
12802     if (gameMode == EditPosition) return;
12803     if (currentMove <= backwardMostMove) {
12804         ClearHighlights();
12805         DrawPosition(full_redraw, boards[currentMove]);
12806         return;
12807     }
12808     if (gameMode == PlayFromGameFile && !pausing)
12809       PauseEvent();
12810
12811     if (moveList[target][0]) {
12812         int fromX, fromY, toX, toY;
12813         toX = moveList[target][2] - AAA;
12814         toY = moveList[target][3] - ONE;
12815         if (moveList[target][1] == '@') {
12816             if (appData.highlightLastMove) {
12817                 SetHighlights(-1, -1, toX, toY);
12818             }
12819         } else {
12820             fromX = moveList[target][0] - AAA;
12821             fromY = moveList[target][1] - ONE;
12822             if (target == currentMove - 1) {
12823                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12824             }
12825             if (appData.highlightLastMove) {
12826                 SetHighlights(fromX, fromY, toX, toY);
12827             }
12828         }
12829     }
12830     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12831         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12832         while (currentMove > target) {
12833             SendToProgram("undo\n", &first);
12834             currentMove--;
12835         }
12836     } else {
12837         currentMove = target;
12838     }
12839
12840     if (gameMode == EditGame || gameMode == EndOfGame) {
12841         whiteTimeRemaining = timeRemaining[0][currentMove];
12842         blackTimeRemaining = timeRemaining[1][currentMove];
12843     }
12844     DisplayBothClocks();
12845     DisplayMove(currentMove - 1);
12846     DrawPosition(full_redraw, boards[currentMove]);
12847     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12848     // [HGM] PV info: routine tests if comment empty
12849     DisplayComment(currentMove - 1, commentList[currentMove]);
12850 }
12851
12852 void
12853 BackwardEvent()
12854 {
12855     if (gameMode == IcsExamining && !pausing) {
12856         SendToICS(ics_prefix);
12857         SendToICS("backward\n");
12858     } else {
12859         BackwardInner(currentMove - 1);
12860     }
12861 }
12862
12863 void
12864 ToStartEvent()
12865 {
12866     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12867         /* to optimize, we temporarily turn off analysis mode while we undo
12868          * all the moves. Otherwise we get analysis output after each undo.
12869          */
12870         if (first.analysisSupport) {
12871           SendToProgram("exit\nforce\n", &first);
12872           first.analyzing = FALSE;
12873         }
12874     }
12875
12876     if (gameMode == IcsExamining && !pausing) {
12877         SendToICS(ics_prefix);
12878         SendToICS("backward 999999\n");
12879     } else {
12880         BackwardInner(backwardMostMove);
12881     }
12882
12883     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12884         /* we have fed all the moves, so reactivate analysis mode */
12885         SendToProgram("analyze\n", &first);
12886         first.analyzing = TRUE;
12887         /*first.maybeThinking = TRUE;*/
12888         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12889     }
12890 }
12891
12892 void
12893 ToNrEvent(int to)
12894 {
12895   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12896   if (to >= forwardMostMove) to = forwardMostMove;
12897   if (to <= backwardMostMove) to = backwardMostMove;
12898   if (to < currentMove) {
12899     BackwardInner(to);
12900   } else {
12901     ForwardInner(to);
12902   }
12903 }
12904
12905 void
12906 RevertEvent(Boolean annotate)
12907 {
12908     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12909         return;
12910     }
12911     if (gameMode != IcsExamining) {
12912         DisplayError(_("You are not examining a game"), 0);
12913         return;
12914     }
12915     if (pausing) {
12916         DisplayError(_("You can't revert while pausing"), 0);
12917         return;
12918     }
12919     SendToICS(ics_prefix);
12920     SendToICS("revert\n");
12921 }
12922
12923 void
12924 RetractMoveEvent()
12925 {
12926     switch (gameMode) {
12927       case MachinePlaysWhite:
12928       case MachinePlaysBlack:
12929         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12930             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12931             return;
12932         }
12933         if (forwardMostMove < 2) return;
12934         currentMove = forwardMostMove = forwardMostMove - 2;
12935         whiteTimeRemaining = timeRemaining[0][currentMove];
12936         blackTimeRemaining = timeRemaining[1][currentMove];
12937         DisplayBothClocks();
12938         DisplayMove(currentMove - 1);
12939         ClearHighlights();/*!! could figure this out*/
12940         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12941         SendToProgram("remove\n", &first);
12942         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12943         break;
12944
12945       case BeginningOfGame:
12946       default:
12947         break;
12948
12949       case IcsPlayingWhite:
12950       case IcsPlayingBlack:
12951         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12952             SendToICS(ics_prefix);
12953             SendToICS("takeback 2\n");
12954         } else {
12955             SendToICS(ics_prefix);
12956             SendToICS("takeback 1\n");
12957         }
12958         break;
12959     }
12960 }
12961
12962 void
12963 MoveNowEvent()
12964 {
12965     ChessProgramState *cps;
12966
12967     switch (gameMode) {
12968       case MachinePlaysWhite:
12969         if (!WhiteOnMove(forwardMostMove)) {
12970             DisplayError(_("It is your turn"), 0);
12971             return;
12972         }
12973         cps = &first;
12974         break;
12975       case MachinePlaysBlack:
12976         if (WhiteOnMove(forwardMostMove)) {
12977             DisplayError(_("It is your turn"), 0);
12978             return;
12979         }
12980         cps = &first;
12981         break;
12982       case TwoMachinesPlay:
12983         if (WhiteOnMove(forwardMostMove) ==
12984             (first.twoMachinesColor[0] == 'w')) {
12985             cps = &first;
12986         } else {
12987             cps = &second;
12988         }
12989         break;
12990       case BeginningOfGame:
12991       default:
12992         return;
12993     }
12994     SendToProgram("?\n", cps);
12995 }
12996
12997 void
12998 TruncateGameEvent()
12999 {
13000     EditGameEvent();
13001     if (gameMode != EditGame) return;
13002     TruncateGame();
13003 }
13004
13005 void
13006 TruncateGame()
13007 {
13008     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13009     if (forwardMostMove > currentMove) {
13010         if (gameInfo.resultDetails != NULL) {
13011             free(gameInfo.resultDetails);
13012             gameInfo.resultDetails = NULL;
13013             gameInfo.result = GameUnfinished;
13014         }
13015         forwardMostMove = currentMove;
13016         HistorySet(parseList, backwardMostMove, forwardMostMove,
13017                    currentMove-1);
13018     }
13019 }
13020
13021 void
13022 HintEvent()
13023 {
13024     if (appData.noChessProgram) return;
13025     switch (gameMode) {
13026       case MachinePlaysWhite:
13027         if (WhiteOnMove(forwardMostMove)) {
13028             DisplayError(_("Wait until your turn"), 0);
13029             return;
13030         }
13031         break;
13032       case BeginningOfGame:
13033       case MachinePlaysBlack:
13034         if (!WhiteOnMove(forwardMostMove)) {
13035             DisplayError(_("Wait until your turn"), 0);
13036             return;
13037         }
13038         break;
13039       default:
13040         DisplayError(_("No hint available"), 0);
13041         return;
13042     }
13043     SendToProgram("hint\n", &first);
13044     hintRequested = TRUE;
13045 }
13046
13047 void
13048 BookEvent()
13049 {
13050     if (appData.noChessProgram) return;
13051     switch (gameMode) {
13052       case MachinePlaysWhite:
13053         if (WhiteOnMove(forwardMostMove)) {
13054             DisplayError(_("Wait until your turn"), 0);
13055             return;
13056         }
13057         break;
13058       case BeginningOfGame:
13059       case MachinePlaysBlack:
13060         if (!WhiteOnMove(forwardMostMove)) {
13061             DisplayError(_("Wait until your turn"), 0);
13062             return;
13063         }
13064         break;
13065       case EditPosition:
13066         EditPositionDone(TRUE);
13067         break;
13068       case TwoMachinesPlay:
13069         return;
13070       default:
13071         break;
13072     }
13073     SendToProgram("bk\n", &first);
13074     bookOutput[0] = NULLCHAR;
13075     bookRequested = TRUE;
13076 }
13077
13078 void
13079 AboutGameEvent()
13080 {
13081     char *tags = PGNTags(&gameInfo);
13082     TagsPopUp(tags, CmailMsg());
13083     free(tags);
13084 }
13085
13086 /* end button procedures */
13087
13088 void
13089 PrintPosition(fp, move)
13090      FILE *fp;
13091      int move;
13092 {
13093     int i, j;
13094
13095     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13096         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13097             char c = PieceToChar(boards[move][i][j]);
13098             fputc(c == 'x' ? '.' : c, fp);
13099             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13100         }
13101     }
13102     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13103       fprintf(fp, "white to play\n");
13104     else
13105       fprintf(fp, "black to play\n");
13106 }
13107
13108 void
13109 PrintOpponents(fp)
13110      FILE *fp;
13111 {
13112     if (gameInfo.white != NULL) {
13113         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13114     } else {
13115         fprintf(fp, "\n");
13116     }
13117 }
13118
13119 /* Find last component of program's own name, using some heuristics */
13120 void
13121 TidyProgramName(prog, host, buf)
13122      char *prog, *host, buf[MSG_SIZ];
13123 {
13124     char *p, *q;
13125     int local = (strcmp(host, "localhost") == 0);
13126     while (!local && (p = strchr(prog, ';')) != NULL) {
13127         p++;
13128         while (*p == ' ') p++;
13129         prog = p;
13130     }
13131     if (*prog == '"' || *prog == '\'') {
13132         q = strchr(prog + 1, *prog);
13133     } else {
13134         q = strchr(prog, ' ');
13135     }
13136     if (q == NULL) q = prog + strlen(prog);
13137     p = q;
13138     while (p >= prog && *p != '/' && *p != '\\') p--;
13139     p++;
13140     if(p == prog && *p == '"') p++;
13141     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13142     memcpy(buf, p, q - p);
13143     buf[q - p] = NULLCHAR;
13144     if (!local) {
13145         strcat(buf, "@");
13146         strcat(buf, host);
13147     }
13148 }
13149
13150 char *
13151 TimeControlTagValue()
13152 {
13153     char buf[MSG_SIZ];
13154     if (!appData.clockMode) {
13155       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13156     } else if (movesPerSession > 0) {
13157       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13158     } else if (timeIncrement == 0) {
13159       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13160     } else {
13161       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13162     }
13163     return StrSave(buf);
13164 }
13165
13166 void
13167 SetGameInfo()
13168 {
13169     /* This routine is used only for certain modes */
13170     VariantClass v = gameInfo.variant;
13171     ChessMove r = GameUnfinished;
13172     char *p = NULL;
13173
13174     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13175         r = gameInfo.result;
13176         p = gameInfo.resultDetails;
13177         gameInfo.resultDetails = NULL;
13178     }
13179     ClearGameInfo(&gameInfo);
13180     gameInfo.variant = v;
13181
13182     switch (gameMode) {
13183       case MachinePlaysWhite:
13184         gameInfo.event = StrSave( appData.pgnEventHeader );
13185         gameInfo.site = StrSave(HostName());
13186         gameInfo.date = PGNDate();
13187         gameInfo.round = StrSave("-");
13188         gameInfo.white = StrSave(first.tidy);
13189         gameInfo.black = StrSave(UserName());
13190         gameInfo.timeControl = TimeControlTagValue();
13191         break;
13192
13193       case MachinePlaysBlack:
13194         gameInfo.event = StrSave( appData.pgnEventHeader );
13195         gameInfo.site = StrSave(HostName());
13196         gameInfo.date = PGNDate();
13197         gameInfo.round = StrSave("-");
13198         gameInfo.white = StrSave(UserName());
13199         gameInfo.black = StrSave(first.tidy);
13200         gameInfo.timeControl = TimeControlTagValue();
13201         break;
13202
13203       case TwoMachinesPlay:
13204         gameInfo.event = StrSave( appData.pgnEventHeader );
13205         gameInfo.site = StrSave(HostName());
13206         gameInfo.date = PGNDate();
13207         if (matchGame > 0) {
13208             char buf[MSG_SIZ];
13209             snprintf(buf, MSG_SIZ, "%d", matchGame);
13210             gameInfo.round = StrSave(buf);
13211         } else {
13212             gameInfo.round = StrSave("-");
13213         }
13214         if (first.twoMachinesColor[0] == 'w') {
13215             gameInfo.white = StrSave(first.tidy);
13216             gameInfo.black = StrSave(second.tidy);
13217         } else {
13218             gameInfo.white = StrSave(second.tidy);
13219             gameInfo.black = StrSave(first.tidy);
13220         }
13221         gameInfo.timeControl = TimeControlTagValue();
13222         break;
13223
13224       case EditGame:
13225         gameInfo.event = StrSave("Edited game");
13226         gameInfo.site = StrSave(HostName());
13227         gameInfo.date = PGNDate();
13228         gameInfo.round = StrSave("-");
13229         gameInfo.white = StrSave("-");
13230         gameInfo.black = StrSave("-");
13231         gameInfo.result = r;
13232         gameInfo.resultDetails = p;
13233         break;
13234
13235       case EditPosition:
13236         gameInfo.event = StrSave("Edited position");
13237         gameInfo.site = StrSave(HostName());
13238         gameInfo.date = PGNDate();
13239         gameInfo.round = StrSave("-");
13240         gameInfo.white = StrSave("-");
13241         gameInfo.black = StrSave("-");
13242         break;
13243
13244       case IcsPlayingWhite:
13245       case IcsPlayingBlack:
13246       case IcsObserving:
13247       case IcsExamining:
13248         break;
13249
13250       case PlayFromGameFile:
13251         gameInfo.event = StrSave("Game from non-PGN file");
13252         gameInfo.site = StrSave(HostName());
13253         gameInfo.date = PGNDate();
13254         gameInfo.round = StrSave("-");
13255         gameInfo.white = StrSave("?");
13256         gameInfo.black = StrSave("?");
13257         break;
13258
13259       default:
13260         break;
13261     }
13262 }
13263
13264 void
13265 ReplaceComment(index, text)
13266      int index;
13267      char *text;
13268 {
13269     int len;
13270
13271     while (*text == '\n') text++;
13272     len = strlen(text);
13273     while (len > 0 && text[len - 1] == '\n') len--;
13274
13275     if (commentList[index] != NULL)
13276       free(commentList[index]);
13277
13278     if (len == 0) {
13279         commentList[index] = NULL;
13280         return;
13281     }
13282   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13283       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13284       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13285     commentList[index] = (char *) malloc(len + 2);
13286     strncpy(commentList[index], text, len);
13287     commentList[index][len] = '\n';
13288     commentList[index][len + 1] = NULLCHAR;
13289   } else {
13290     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13291     char *p;
13292     commentList[index] = (char *) malloc(len + 7);
13293     safeStrCpy(commentList[index], "{\n", 3);
13294     safeStrCpy(commentList[index]+2, text, len+1);
13295     commentList[index][len+2] = NULLCHAR;
13296     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13297     strcat(commentList[index], "\n}\n");
13298   }
13299 }
13300
13301 void
13302 CrushCRs(text)
13303      char *text;
13304 {
13305   char *p = text;
13306   char *q = text;
13307   char ch;
13308
13309   do {
13310     ch = *p++;
13311     if (ch == '\r') continue;
13312     *q++ = ch;
13313   } while (ch != '\0');
13314 }
13315
13316 void
13317 AppendComment(index, text, addBraces)
13318      int index;
13319      char *text;
13320      Boolean addBraces; // [HGM] braces: tells if we should add {}
13321 {
13322     int oldlen, len;
13323     char *old;
13324
13325 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13326     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13327
13328     CrushCRs(text);
13329     while (*text == '\n') text++;
13330     len = strlen(text);
13331     while (len > 0 && text[len - 1] == '\n') len--;
13332
13333     if (len == 0) return;
13334
13335     if (commentList[index] != NULL) {
13336         old = commentList[index];
13337         oldlen = strlen(old);
13338         while(commentList[index][oldlen-1] ==  '\n')
13339           commentList[index][--oldlen] = NULLCHAR;
13340         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13341         safeStrCpy(commentList[index], old, oldlen);
13342         free(old);
13343         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13344         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13345           if(addBraces) addBraces = FALSE; else { text++; len--; }
13346           while (*text == '\n') { text++; len--; }
13347           commentList[index][--oldlen] = NULLCHAR;
13348       }
13349         if(addBraces) strcat(commentList[index], "\n{\n");
13350         else          strcat(commentList[index], "\n");
13351         strcat(commentList[index], text);
13352         if(addBraces) strcat(commentList[index], "\n}\n");
13353         else          strcat(commentList[index], "\n");
13354     } else {
13355         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13356         if(addBraces)
13357           safeStrCpy(commentList[index], "{\n", 3);
13358         else commentList[index][0] = NULLCHAR;
13359         strcat(commentList[index], text);
13360         strcat(commentList[index], "\n");
13361         if(addBraces) strcat(commentList[index], "}\n");
13362     }
13363 }
13364
13365 static char * FindStr( char * text, char * sub_text )
13366 {
13367     char * result = strstr( text, sub_text );
13368
13369     if( result != NULL ) {
13370         result += strlen( sub_text );
13371     }
13372
13373     return result;
13374 }
13375
13376 /* [AS] Try to extract PV info from PGN comment */
13377 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13378 char *GetInfoFromComment( int index, char * text )
13379 {
13380     char * sep = text;
13381
13382     if( text != NULL && index > 0 ) {
13383         int score = 0;
13384         int depth = 0;
13385         int time = -1, sec = 0, deci;
13386         char * s_eval = FindStr( text, "[%eval " );
13387         char * s_emt = FindStr( text, "[%emt " );
13388
13389         if( s_eval != NULL || s_emt != NULL ) {
13390             /* New style */
13391             char delim;
13392
13393             if( s_eval != NULL ) {
13394                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13395                     return text;
13396                 }
13397
13398                 if( delim != ']' ) {
13399                     return text;
13400                 }
13401             }
13402
13403             if( s_emt != NULL ) {
13404             }
13405                 return text;
13406         }
13407         else {
13408             /* We expect something like: [+|-]nnn.nn/dd */
13409             int score_lo = 0;
13410
13411             if(*text != '{') return text; // [HGM] braces: must be normal comment
13412
13413             sep = strchr( text, '/' );
13414             if( sep == NULL || sep < (text+4) ) {
13415                 return text;
13416             }
13417
13418             time = -1; sec = -1; deci = -1;
13419             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13420                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13421                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13422                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13423                 return text;
13424             }
13425
13426             if( score_lo < 0 || score_lo >= 100 ) {
13427                 return text;
13428             }
13429
13430             if(sec >= 0) time = 600*time + 10*sec; else
13431             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13432
13433             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13434
13435             /* [HGM] PV time: now locate end of PV info */
13436             while( *++sep >= '0' && *sep <= '9'); // strip depth
13437             if(time >= 0)
13438             while( *++sep >= '0' && *sep <= '9'); // strip time
13439             if(sec >= 0)
13440             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13441             if(deci >= 0)
13442             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13443             while(*sep == ' ') sep++;
13444         }
13445
13446         if( depth <= 0 ) {
13447             return text;
13448         }
13449
13450         if( time < 0 ) {
13451             time = -1;
13452         }
13453
13454         pvInfoList[index-1].depth = depth;
13455         pvInfoList[index-1].score = score;
13456         pvInfoList[index-1].time  = 10*time; // centi-sec
13457         if(*sep == '}') *sep = 0; else *--sep = '{';
13458     }
13459     return sep;
13460 }
13461
13462 void
13463 SendToProgram(message, cps)
13464      char *message;
13465      ChessProgramState *cps;
13466 {
13467     int count, outCount, error;
13468     char buf[MSG_SIZ];
13469
13470     if (cps->pr == NULL) return;
13471     Attention(cps);
13472
13473     if (appData.debugMode) {
13474         TimeMark now;
13475         GetTimeMark(&now);
13476         fprintf(debugFP, "%ld >%-6s: %s",
13477                 SubtractTimeMarks(&now, &programStartTime),
13478                 cps->which, message);
13479     }
13480
13481     count = strlen(message);
13482     outCount = OutputToProcess(cps->pr, message, count, &error);
13483     if (outCount < count && !exiting
13484                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13485       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13486         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13487             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13488                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13489                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13490             } else {
13491                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13492             }
13493             gameInfo.resultDetails = StrSave(buf);
13494         }
13495         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13496     }
13497 }
13498
13499 void
13500 ReceiveFromProgram(isr, closure, message, count, error)
13501      InputSourceRef isr;
13502      VOIDSTAR closure;
13503      char *message;
13504      int count;
13505      int error;
13506 {
13507     char *end_str;
13508     char buf[MSG_SIZ];
13509     ChessProgramState *cps = (ChessProgramState *)closure;
13510
13511     if (isr != cps->isr) return; /* Killed intentionally */
13512     if (count <= 0) {
13513         if (count == 0) {
13514             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13515                     cps->which, cps->program);
13516         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13517                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13518                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13519                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13520                 } else {
13521                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13522                 }
13523                 gameInfo.resultDetails = StrSave(buf);
13524             }
13525             RemoveInputSource(cps->isr);
13526             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13527         } else {
13528             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13529                     cps->which, cps->program);
13530             RemoveInputSource(cps->isr);
13531
13532             /* [AS] Program is misbehaving badly... kill it */
13533             if( count == -2 ) {
13534                 DestroyChildProcess( cps->pr, 9 );
13535                 cps->pr = NoProc;
13536             }
13537
13538             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13539         }
13540         return;
13541     }
13542
13543     if ((end_str = strchr(message, '\r')) != NULL)
13544       *end_str = NULLCHAR;
13545     if ((end_str = strchr(message, '\n')) != NULL)
13546       *end_str = NULLCHAR;
13547
13548     if (appData.debugMode) {
13549         TimeMark now; int print = 1;
13550         char *quote = ""; char c; int i;
13551
13552         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13553                 char start = message[0];
13554                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13555                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13556                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13557                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13558                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13559                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13560                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13561                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13562                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13563                     print = (appData.engineComments >= 2);
13564                 }
13565                 message[0] = start; // restore original message
13566         }
13567         if(print) {
13568                 GetTimeMark(&now);
13569                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13570                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13571                         quote,
13572                         message);
13573         }
13574     }
13575
13576     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13577     if (appData.icsEngineAnalyze) {
13578         if (strstr(message, "whisper") != NULL ||
13579              strstr(message, "kibitz") != NULL ||
13580             strstr(message, "tellics") != NULL) return;
13581     }
13582
13583     HandleMachineMove(message, cps);
13584 }
13585
13586
13587 void
13588 SendTimeControl(cps, mps, tc, inc, sd, st)
13589      ChessProgramState *cps;
13590      int mps, inc, sd, st;
13591      long tc;
13592 {
13593     char buf[MSG_SIZ];
13594     int seconds;
13595
13596     if( timeControl_2 > 0 ) {
13597         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13598             tc = timeControl_2;
13599         }
13600     }
13601     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13602     inc /= cps->timeOdds;
13603     st  /= cps->timeOdds;
13604
13605     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13606
13607     if (st > 0) {
13608       /* Set exact time per move, normally using st command */
13609       if (cps->stKludge) {
13610         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13611         seconds = st % 60;
13612         if (seconds == 0) {
13613           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13614         } else {
13615           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13616         }
13617       } else {
13618         snprintf(buf, MSG_SIZ, "st %d\n", st);
13619       }
13620     } else {
13621       /* Set conventional or incremental time control, using level command */
13622       if (seconds == 0) {
13623         /* Note old gnuchess bug -- minutes:seconds used to not work.
13624            Fixed in later versions, but still avoid :seconds
13625            when seconds is 0. */
13626         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13627       } else {
13628         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13629                  seconds, inc/1000.);
13630       }
13631     }
13632     SendToProgram(buf, cps);
13633
13634     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13635     /* Orthogonally, limit search to given depth */
13636     if (sd > 0) {
13637       if (cps->sdKludge) {
13638         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13639       } else {
13640         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13641       }
13642       SendToProgram(buf, cps);
13643     }
13644
13645     if(cps->nps > 0) { /* [HGM] nps */
13646         if(cps->supportsNPS == FALSE)
13647           cps->nps = -1; // don't use if engine explicitly says not supported!
13648         else {
13649           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13650           SendToProgram(buf, cps);
13651         }
13652     }
13653 }
13654
13655 ChessProgramState *WhitePlayer()
13656 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13657 {
13658     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13659        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13660         return &second;
13661     return &first;
13662 }
13663
13664 void
13665 SendTimeRemaining(cps, machineWhite)
13666      ChessProgramState *cps;
13667      int /*boolean*/ machineWhite;
13668 {
13669     char message[MSG_SIZ];
13670     long time, otime;
13671
13672     /* Note: this routine must be called when the clocks are stopped
13673        or when they have *just* been set or switched; otherwise
13674        it will be off by the time since the current tick started.
13675     */
13676     if (machineWhite) {
13677         time = whiteTimeRemaining / 10;
13678         otime = blackTimeRemaining / 10;
13679     } else {
13680         time = blackTimeRemaining / 10;
13681         otime = whiteTimeRemaining / 10;
13682     }
13683     /* [HGM] translate opponent's time by time-odds factor */
13684     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13685     if (appData.debugMode) {
13686         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13687     }
13688
13689     if (time <= 0) time = 1;
13690     if (otime <= 0) otime = 1;
13691
13692     snprintf(message, MSG_SIZ, "time %ld\n", time);
13693     SendToProgram(message, cps);
13694
13695     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13696     SendToProgram(message, cps);
13697 }
13698
13699 int
13700 BoolFeature(p, name, loc, cps)
13701      char **p;
13702      char *name;
13703      int *loc;
13704      ChessProgramState *cps;
13705 {
13706   char buf[MSG_SIZ];
13707   int len = strlen(name);
13708   int val;
13709
13710   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13711     (*p) += len + 1;
13712     sscanf(*p, "%d", &val);
13713     *loc = (val != 0);
13714     while (**p && **p != ' ')
13715       (*p)++;
13716     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13717     SendToProgram(buf, cps);
13718     return TRUE;
13719   }
13720   return FALSE;
13721 }
13722
13723 int
13724 IntFeature(p, name, loc, cps)
13725      char **p;
13726      char *name;
13727      int *loc;
13728      ChessProgramState *cps;
13729 {
13730   char buf[MSG_SIZ];
13731   int len = strlen(name);
13732   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13733     (*p) += len + 1;
13734     sscanf(*p, "%d", loc);
13735     while (**p && **p != ' ') (*p)++;
13736     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13737     SendToProgram(buf, cps);
13738     return TRUE;
13739   }
13740   return FALSE;
13741 }
13742
13743 int
13744 StringFeature(p, name, loc, cps)
13745      char **p;
13746      char *name;
13747      char loc[];
13748      ChessProgramState *cps;
13749 {
13750   char buf[MSG_SIZ];
13751   int len = strlen(name);
13752   if (strncmp((*p), name, len) == 0
13753       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13754     (*p) += len + 2;
13755     sscanf(*p, "%[^\"]", loc);
13756     while (**p && **p != '\"') (*p)++;
13757     if (**p == '\"') (*p)++;
13758     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13759     SendToProgram(buf, cps);
13760     return TRUE;
13761   }
13762   return FALSE;
13763 }
13764
13765 int
13766 ParseOption(Option *opt, ChessProgramState *cps)
13767 // [HGM] options: process the string that defines an engine option, and determine
13768 // name, type, default value, and allowed value range
13769 {
13770         char *p, *q, buf[MSG_SIZ];
13771         int n, min = (-1)<<31, max = 1<<31, def;
13772
13773         if(p = strstr(opt->name, " -spin ")) {
13774             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13775             if(max < min) max = min; // enforce consistency
13776             if(def < min) def = min;
13777             if(def > max) def = max;
13778             opt->value = def;
13779             opt->min = min;
13780             opt->max = max;
13781             opt->type = Spin;
13782         } else if((p = strstr(opt->name, " -slider "))) {
13783             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13784             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13785             if(max < min) max = min; // enforce consistency
13786             if(def < min) def = min;
13787             if(def > max) def = max;
13788             opt->value = def;
13789             opt->min = min;
13790             opt->max = max;
13791             opt->type = Spin; // Slider;
13792         } else if((p = strstr(opt->name, " -string "))) {
13793             opt->textValue = p+9;
13794             opt->type = TextBox;
13795         } else if((p = strstr(opt->name, " -file "))) {
13796             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13797             opt->textValue = p+7;
13798             opt->type = TextBox; // FileName;
13799         } else if((p = strstr(opt->name, " -path "))) {
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; // PathName;
13803         } else if(p = strstr(opt->name, " -check ")) {
13804             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13805             opt->value = (def != 0);
13806             opt->type = CheckBox;
13807         } else if(p = strstr(opt->name, " -combo ")) {
13808             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13809             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13810             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13811             opt->value = n = 0;
13812             while(q = StrStr(q, " /// ")) {
13813                 n++; *q = 0;    // count choices, and null-terminate each of them
13814                 q += 5;
13815                 if(*q == '*') { // remember default, which is marked with * prefix
13816                     q++;
13817                     opt->value = n;
13818                 }
13819                 cps->comboList[cps->comboCnt++] = q;
13820             }
13821             cps->comboList[cps->comboCnt++] = NULL;
13822             opt->max = n + 1;
13823             opt->type = ComboBox;
13824         } else if(p = strstr(opt->name, " -button")) {
13825             opt->type = Button;
13826         } else if(p = strstr(opt->name, " -save")) {
13827             opt->type = SaveButton;
13828         } else return FALSE;
13829         *p = 0; // terminate option name
13830         // now look if the command-line options define a setting for this engine option.
13831         if(cps->optionSettings && cps->optionSettings[0])
13832             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13833         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13834           snprintf(buf, MSG_SIZ, "option %s", p);
13835                 if(p = strstr(buf, ",")) *p = 0;
13836                 if(q = strchr(buf, '=')) switch(opt->type) {
13837                     case ComboBox:
13838                         for(n=0; n<opt->max; n++)
13839                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13840                         break;
13841                     case TextBox:
13842                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13843                         break;
13844                     case Spin:
13845                     case CheckBox:
13846                         opt->value = atoi(q+1);
13847                     default:
13848                         break;
13849                 }
13850                 strcat(buf, "\n");
13851                 SendToProgram(buf, cps);
13852         }
13853         return TRUE;
13854 }
13855
13856 void
13857 FeatureDone(cps, val)
13858      ChessProgramState* cps;
13859      int val;
13860 {
13861   DelayedEventCallback cb = GetDelayedEvent();
13862   if ((cb == InitBackEnd3 && cps == &first) ||
13863       (cb == SettingsMenuIfReady && cps == &second) ||
13864       (cb == TwoMachinesEventIfReady && cps == &second)) {
13865     CancelDelayedEvent();
13866     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13867   }
13868   cps->initDone = val;
13869 }
13870
13871 /* Parse feature command from engine */
13872 void
13873 ParseFeatures(args, cps)
13874      char* args;
13875      ChessProgramState *cps;
13876 {
13877   char *p = args;
13878   char *q;
13879   int val;
13880   char buf[MSG_SIZ];
13881
13882   for (;;) {
13883     while (*p == ' ') p++;
13884     if (*p == NULLCHAR) return;
13885
13886     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13887     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13888     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13889     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13890     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13891     if (BoolFeature(&p, "reuse", &val, cps)) {
13892       /* Engine can disable reuse, but can't enable it if user said no */
13893       if (!val) cps->reuse = FALSE;
13894       continue;
13895     }
13896     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13897     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13898       if (gameMode == TwoMachinesPlay) {
13899         DisplayTwoMachinesTitle();
13900       } else {
13901         DisplayTitle("");
13902       }
13903       continue;
13904     }
13905     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13906     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13907     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13908     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13909     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13910     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13911     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13912     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13913     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13914     if (IntFeature(&p, "done", &val, cps)) {
13915       FeatureDone(cps, val);
13916       continue;
13917     }
13918     /* Added by Tord: */
13919     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13920     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13921     /* End of additions by Tord */
13922
13923     /* [HGM] added features: */
13924     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13925     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13926     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13927     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13928     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13929     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13930     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13931         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13932           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13933             SendToProgram(buf, cps);
13934             continue;
13935         }
13936         if(cps->nrOptions >= MAX_OPTIONS) {
13937             cps->nrOptions--;
13938             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13939             DisplayError(buf, 0);
13940         }
13941         continue;
13942     }
13943     /* End of additions by HGM */
13944
13945     /* unknown feature: complain and skip */
13946     q = p;
13947     while (*q && *q != '=') q++;
13948     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13949     SendToProgram(buf, cps);
13950     p = q;
13951     if (*p == '=') {
13952       p++;
13953       if (*p == '\"') {
13954         p++;
13955         while (*p && *p != '\"') p++;
13956         if (*p == '\"') p++;
13957       } else {
13958         while (*p && *p != ' ') p++;
13959       }
13960     }
13961   }
13962
13963 }
13964
13965 void
13966 PeriodicUpdatesEvent(newState)
13967      int newState;
13968 {
13969     if (newState == appData.periodicUpdates)
13970       return;
13971
13972     appData.periodicUpdates=newState;
13973
13974     /* Display type changes, so update it now */
13975 //    DisplayAnalysis();
13976
13977     /* Get the ball rolling again... */
13978     if (newState) {
13979         AnalysisPeriodicEvent(1);
13980         StartAnalysisClock();
13981     }
13982 }
13983
13984 void
13985 PonderNextMoveEvent(newState)
13986      int newState;
13987 {
13988     if (newState == appData.ponderNextMove) return;
13989     if (gameMode == EditPosition) EditPositionDone(TRUE);
13990     if (newState) {
13991         SendToProgram("hard\n", &first);
13992         if (gameMode == TwoMachinesPlay) {
13993             SendToProgram("hard\n", &second);
13994         }
13995     } else {
13996         SendToProgram("easy\n", &first);
13997         thinkOutput[0] = NULLCHAR;
13998         if (gameMode == TwoMachinesPlay) {
13999             SendToProgram("easy\n", &second);
14000         }
14001     }
14002     appData.ponderNextMove = newState;
14003 }
14004
14005 void
14006 NewSettingEvent(option, feature, command, value)
14007      char *command;
14008      int option, value, *feature;
14009 {
14010     char buf[MSG_SIZ];
14011
14012     if (gameMode == EditPosition) EditPositionDone(TRUE);
14013     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14014     if(feature == NULL || *feature) SendToProgram(buf, &first);
14015     if (gameMode == TwoMachinesPlay) {
14016         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14017     }
14018 }
14019
14020 void
14021 ShowThinkingEvent()
14022 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14023 {
14024     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14025     int newState = appData.showThinking
14026         // [HGM] thinking: other features now need thinking output as well
14027         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14028
14029     if (oldState == newState) return;
14030     oldState = newState;
14031     if (gameMode == EditPosition) EditPositionDone(TRUE);
14032     if (oldState) {
14033         SendToProgram("post\n", &first);
14034         if (gameMode == TwoMachinesPlay) {
14035             SendToProgram("post\n", &second);
14036         }
14037     } else {
14038         SendToProgram("nopost\n", &first);
14039         thinkOutput[0] = NULLCHAR;
14040         if (gameMode == TwoMachinesPlay) {
14041             SendToProgram("nopost\n", &second);
14042         }
14043     }
14044 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14045 }
14046
14047 void
14048 AskQuestionEvent(title, question, replyPrefix, which)
14049      char *title; char *question; char *replyPrefix; char *which;
14050 {
14051   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14052   if (pr == NoProc) return;
14053   AskQuestion(title, question, replyPrefix, pr);
14054 }
14055
14056 void
14057 DisplayMove(moveNumber)
14058      int moveNumber;
14059 {
14060     char message[MSG_SIZ];
14061     char res[MSG_SIZ];
14062     char cpThinkOutput[MSG_SIZ];
14063
14064     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14065
14066     if (moveNumber == forwardMostMove - 1 ||
14067         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14068
14069         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14070
14071         if (strchr(cpThinkOutput, '\n')) {
14072             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14073         }
14074     } else {
14075         *cpThinkOutput = NULLCHAR;
14076     }
14077
14078     /* [AS] Hide thinking from human user */
14079     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14080         *cpThinkOutput = NULLCHAR;
14081         if( thinkOutput[0] != NULLCHAR ) {
14082             int i;
14083
14084             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14085                 cpThinkOutput[i] = '.';
14086             }
14087             cpThinkOutput[i] = NULLCHAR;
14088             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14089         }
14090     }
14091
14092     if (moveNumber == forwardMostMove - 1 &&
14093         gameInfo.resultDetails != NULL) {
14094         if (gameInfo.resultDetails[0] == NULLCHAR) {
14095           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14096         } else {
14097           snprintf(res, MSG_SIZ, " {%s} %s",
14098                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14099         }
14100     } else {
14101         res[0] = NULLCHAR;
14102     }
14103
14104     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14105         DisplayMessage(res, cpThinkOutput);
14106     } else {
14107       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14108                 WhiteOnMove(moveNumber) ? " " : ".. ",
14109                 parseList[moveNumber], res);
14110         DisplayMessage(message, cpThinkOutput);
14111     }
14112 }
14113
14114 void
14115 DisplayComment(moveNumber, text)
14116      int moveNumber;
14117      char *text;
14118 {
14119     char title[MSG_SIZ];
14120     char buf[8000]; // comment can be long!
14121     int score, depth;
14122
14123     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14124       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14125     } else {
14126       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14127               WhiteOnMove(moveNumber) ? " " : ".. ",
14128               parseList[moveNumber]);
14129     }
14130     // [HGM] PV info: display PV info together with (or as) comment
14131     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14132       if(text == NULL) text = "";
14133       score = pvInfoList[moveNumber].score;
14134       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14135               depth, (pvInfoList[moveNumber].time+50)/100, text);
14136       text = buf;
14137     }
14138     if (text != NULL && (appData.autoDisplayComment || commentUp))
14139         CommentPopUp(title, text);
14140 }
14141
14142 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14143  * might be busy thinking or pondering.  It can be omitted if your
14144  * gnuchess is configured to stop thinking immediately on any user
14145  * input.  However, that gnuchess feature depends on the FIONREAD
14146  * ioctl, which does not work properly on some flavors of Unix.
14147  */
14148 void
14149 Attention(cps)
14150      ChessProgramState *cps;
14151 {
14152 #if ATTENTION
14153     if (!cps->useSigint) return;
14154     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14155     switch (gameMode) {
14156       case MachinePlaysWhite:
14157       case MachinePlaysBlack:
14158       case TwoMachinesPlay:
14159       case IcsPlayingWhite:
14160       case IcsPlayingBlack:
14161       case AnalyzeMode:
14162       case AnalyzeFile:
14163         /* Skip if we know it isn't thinking */
14164         if (!cps->maybeThinking) return;
14165         if (appData.debugMode)
14166           fprintf(debugFP, "Interrupting %s\n", cps->which);
14167         InterruptChildProcess(cps->pr);
14168         cps->maybeThinking = FALSE;
14169         break;
14170       default:
14171         break;
14172     }
14173 #endif /*ATTENTION*/
14174 }
14175
14176 int
14177 CheckFlags()
14178 {
14179     if (whiteTimeRemaining <= 0) {
14180         if (!whiteFlag) {
14181             whiteFlag = TRUE;
14182             if (appData.icsActive) {
14183                 if (appData.autoCallFlag &&
14184                     gameMode == IcsPlayingBlack && !blackFlag) {
14185                   SendToICS(ics_prefix);
14186                   SendToICS("flag\n");
14187                 }
14188             } else {
14189                 if (blackFlag) {
14190                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14191                 } else {
14192                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14193                     if (appData.autoCallFlag) {
14194                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14195                         return TRUE;
14196                     }
14197                 }
14198             }
14199         }
14200     }
14201     if (blackTimeRemaining <= 0) {
14202         if (!blackFlag) {
14203             blackFlag = TRUE;
14204             if (appData.icsActive) {
14205                 if (appData.autoCallFlag &&
14206                     gameMode == IcsPlayingWhite && !whiteFlag) {
14207                   SendToICS(ics_prefix);
14208                   SendToICS("flag\n");
14209                 }
14210             } else {
14211                 if (whiteFlag) {
14212                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14213                 } else {
14214                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14215                     if (appData.autoCallFlag) {
14216                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14217                         return TRUE;
14218                     }
14219                 }
14220             }
14221         }
14222     }
14223     return FALSE;
14224 }
14225
14226 void
14227 CheckTimeControl()
14228 {
14229     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14230         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14231
14232     /*
14233      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14234      */
14235     if ( !WhiteOnMove(forwardMostMove) ) {
14236         /* White made time control */
14237         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14238         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14239         /* [HGM] time odds: correct new time quota for time odds! */
14240                                             / WhitePlayer()->timeOdds;
14241         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14242     } else {
14243         lastBlack -= blackTimeRemaining;
14244         /* Black made time control */
14245         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14246                                             / WhitePlayer()->other->timeOdds;
14247         lastWhite = whiteTimeRemaining;
14248     }
14249 }
14250
14251 void
14252 DisplayBothClocks()
14253 {
14254     int wom = gameMode == EditPosition ?
14255       !blackPlaysFirst : WhiteOnMove(currentMove);
14256     DisplayWhiteClock(whiteTimeRemaining, wom);
14257     DisplayBlackClock(blackTimeRemaining, !wom);
14258 }
14259
14260
14261 /* Timekeeping seems to be a portability nightmare.  I think everyone
14262    has ftime(), but I'm really not sure, so I'm including some ifdefs
14263    to use other calls if you don't.  Clocks will be less accurate if
14264    you have neither ftime nor gettimeofday.
14265 */
14266
14267 /* VS 2008 requires the #include outside of the function */
14268 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14269 #include <sys/timeb.h>
14270 #endif
14271
14272 /* Get the current time as a TimeMark */
14273 void
14274 GetTimeMark(tm)
14275      TimeMark *tm;
14276 {
14277 #if HAVE_GETTIMEOFDAY
14278
14279     struct timeval timeVal;
14280     struct timezone timeZone;
14281
14282     gettimeofday(&timeVal, &timeZone);
14283     tm->sec = (long) timeVal.tv_sec;
14284     tm->ms = (int) (timeVal.tv_usec / 1000L);
14285
14286 #else /*!HAVE_GETTIMEOFDAY*/
14287 #if HAVE_FTIME
14288
14289 // include <sys/timeb.h> / moved to just above start of function
14290     struct timeb timeB;
14291
14292     ftime(&timeB);
14293     tm->sec = (long) timeB.time;
14294     tm->ms = (int) timeB.millitm;
14295
14296 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14297     tm->sec = (long) time(NULL);
14298     tm->ms = 0;
14299 #endif
14300 #endif
14301 }
14302
14303 /* Return the difference in milliseconds between two
14304    time marks.  We assume the difference will fit in a long!
14305 */
14306 long
14307 SubtractTimeMarks(tm2, tm1)
14308      TimeMark *tm2, *tm1;
14309 {
14310     return 1000L*(tm2->sec - tm1->sec) +
14311            (long) (tm2->ms - tm1->ms);
14312 }
14313
14314
14315 /*
14316  * Code to manage the game clocks.
14317  *
14318  * In tournament play, black starts the clock and then white makes a move.
14319  * We give the human user a slight advantage if he is playing white---the
14320  * clocks don't run until he makes his first move, so it takes zero time.
14321  * Also, we don't account for network lag, so we could get out of sync
14322  * with GNU Chess's clock -- but then, referees are always right.
14323  */
14324
14325 static TimeMark tickStartTM;
14326 static long intendedTickLength;
14327
14328 long
14329 NextTickLength(timeRemaining)
14330      long timeRemaining;
14331 {
14332     long nominalTickLength, nextTickLength;
14333
14334     if (timeRemaining > 0L && timeRemaining <= 10000L)
14335       nominalTickLength = 100L;
14336     else
14337       nominalTickLength = 1000L;
14338     nextTickLength = timeRemaining % nominalTickLength;
14339     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14340
14341     return nextTickLength;
14342 }
14343
14344 /* Adjust clock one minute up or down */
14345 void
14346 AdjustClock(Boolean which, int dir)
14347 {
14348     if(which) blackTimeRemaining += 60000*dir;
14349     else      whiteTimeRemaining += 60000*dir;
14350     DisplayBothClocks();
14351 }
14352
14353 /* Stop clocks and reset to a fresh time control */
14354 void
14355 ResetClocks()
14356 {
14357     (void) StopClockTimer();
14358     if (appData.icsActive) {
14359         whiteTimeRemaining = blackTimeRemaining = 0;
14360     } else if (searchTime) {
14361         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14362         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14363     } else { /* [HGM] correct new time quote for time odds */
14364         whiteTC = blackTC = fullTimeControlString;
14365         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14366         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14367     }
14368     if (whiteFlag || blackFlag) {
14369         DisplayTitle("");
14370         whiteFlag = blackFlag = FALSE;
14371     }
14372     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14373     DisplayBothClocks();
14374 }
14375
14376 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14377
14378 /* Decrement running clock by amount of time that has passed */
14379 void
14380 DecrementClocks()
14381 {
14382     long timeRemaining;
14383     long lastTickLength, fudge;
14384     TimeMark now;
14385
14386     if (!appData.clockMode) return;
14387     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14388
14389     GetTimeMark(&now);
14390
14391     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14392
14393     /* Fudge if we woke up a little too soon */
14394     fudge = intendedTickLength - lastTickLength;
14395     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14396
14397     if (WhiteOnMove(forwardMostMove)) {
14398         if(whiteNPS >= 0) lastTickLength = 0;
14399         timeRemaining = whiteTimeRemaining -= lastTickLength;
14400         if(timeRemaining < 0 && !appData.icsActive) {
14401             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14402             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14403                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14404                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14405             }
14406         }
14407         DisplayWhiteClock(whiteTimeRemaining - fudge,
14408                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14409     } else {
14410         if(blackNPS >= 0) lastTickLength = 0;
14411         timeRemaining = blackTimeRemaining -= lastTickLength;
14412         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14413             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14414             if(suddenDeath) {
14415                 blackStartMove = forwardMostMove;
14416                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14417             }
14418         }
14419         DisplayBlackClock(blackTimeRemaining - fudge,
14420                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14421     }
14422     if (CheckFlags()) return;
14423
14424     tickStartTM = now;
14425     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14426     StartClockTimer(intendedTickLength);
14427
14428     /* if the time remaining has fallen below the alarm threshold, sound the
14429      * alarm. if the alarm has sounded and (due to a takeback or time control
14430      * with increment) the time remaining has increased to a level above the
14431      * threshold, reset the alarm so it can sound again.
14432      */
14433
14434     if (appData.icsActive && appData.icsAlarm) {
14435
14436         /* make sure we are dealing with the user's clock */
14437         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14438                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14439            )) return;
14440
14441         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14442             alarmSounded = FALSE;
14443         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14444             PlayAlarmSound();
14445             alarmSounded = TRUE;
14446         }
14447     }
14448 }
14449
14450
14451 /* A player has just moved, so stop the previously running
14452    clock and (if in clock mode) start the other one.
14453    We redisplay both clocks in case we're in ICS mode, because
14454    ICS gives us an update to both clocks after every move.
14455    Note that this routine is called *after* forwardMostMove
14456    is updated, so the last fractional tick must be subtracted
14457    from the color that is *not* on move now.
14458 */
14459 void
14460 SwitchClocks(int newMoveNr)
14461 {
14462     long lastTickLength;
14463     TimeMark now;
14464     int flagged = FALSE;
14465
14466     GetTimeMark(&now);
14467
14468     if (StopClockTimer() && appData.clockMode) {
14469         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14470         if (!WhiteOnMove(forwardMostMove)) {
14471             if(blackNPS >= 0) lastTickLength = 0;
14472             blackTimeRemaining -= lastTickLength;
14473            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14474 //         if(pvInfoList[forwardMostMove-1].time == -1)
14475                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14476                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14477         } else {
14478            if(whiteNPS >= 0) lastTickLength = 0;
14479            whiteTimeRemaining -= lastTickLength;
14480            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14481 //         if(pvInfoList[forwardMostMove-1].time == -1)
14482                  pvInfoList[forwardMostMove-1].time =
14483                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14484         }
14485         flagged = CheckFlags();
14486     }
14487     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14488     CheckTimeControl();
14489
14490     if (flagged || !appData.clockMode) return;
14491
14492     switch (gameMode) {
14493       case MachinePlaysBlack:
14494       case MachinePlaysWhite:
14495       case BeginningOfGame:
14496         if (pausing) return;
14497         break;
14498
14499       case EditGame:
14500       case PlayFromGameFile:
14501       case IcsExamining:
14502         return;
14503
14504       default:
14505         break;
14506     }
14507
14508     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14509         if(WhiteOnMove(forwardMostMove))
14510              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14511         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14512     }
14513
14514     tickStartTM = now;
14515     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14516       whiteTimeRemaining : blackTimeRemaining);
14517     StartClockTimer(intendedTickLength);
14518 }
14519
14520
14521 /* Stop both clocks */
14522 void
14523 StopClocks()
14524 {
14525     long lastTickLength;
14526     TimeMark now;
14527
14528     if (!StopClockTimer()) return;
14529     if (!appData.clockMode) return;
14530
14531     GetTimeMark(&now);
14532
14533     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14534     if (WhiteOnMove(forwardMostMove)) {
14535         if(whiteNPS >= 0) lastTickLength = 0;
14536         whiteTimeRemaining -= lastTickLength;
14537         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14538     } else {
14539         if(blackNPS >= 0) lastTickLength = 0;
14540         blackTimeRemaining -= lastTickLength;
14541         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14542     }
14543     CheckFlags();
14544 }
14545
14546 /* Start clock of player on move.  Time may have been reset, so
14547    if clock is already running, stop and restart it. */
14548 void
14549 StartClocks()
14550 {
14551     (void) StopClockTimer(); /* in case it was running already */
14552     DisplayBothClocks();
14553     if (CheckFlags()) return;
14554
14555     if (!appData.clockMode) return;
14556     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14557
14558     GetTimeMark(&tickStartTM);
14559     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14560       whiteTimeRemaining : blackTimeRemaining);
14561
14562    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14563     whiteNPS = blackNPS = -1;
14564     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14565        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14566         whiteNPS = first.nps;
14567     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14568        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14569         blackNPS = first.nps;
14570     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14571         whiteNPS = second.nps;
14572     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14573         blackNPS = second.nps;
14574     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14575
14576     StartClockTimer(intendedTickLength);
14577 }
14578
14579 char *
14580 TimeString(ms)
14581      long ms;
14582 {
14583     long second, minute, hour, day;
14584     char *sign = "";
14585     static char buf[32];
14586
14587     if (ms > 0 && ms <= 9900) {
14588       /* convert milliseconds to tenths, rounding up */
14589       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14590
14591       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14592       return buf;
14593     }
14594
14595     /* convert milliseconds to seconds, rounding up */
14596     /* use floating point to avoid strangeness of integer division
14597        with negative dividends on many machines */
14598     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14599
14600     if (second < 0) {
14601         sign = "-";
14602         second = -second;
14603     }
14604
14605     day = second / (60 * 60 * 24);
14606     second = second % (60 * 60 * 24);
14607     hour = second / (60 * 60);
14608     second = second % (60 * 60);
14609     minute = second / 60;
14610     second = second % 60;
14611
14612     if (day > 0)
14613       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14614               sign, day, hour, minute, second);
14615     else if (hour > 0)
14616       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14617     else
14618       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14619
14620     return buf;
14621 }
14622
14623
14624 /*
14625  * This is necessary because some C libraries aren't ANSI C compliant yet.
14626  */
14627 char *
14628 StrStr(string, match)
14629      char *string, *match;
14630 {
14631     int i, length;
14632
14633     length = strlen(match);
14634
14635     for (i = strlen(string) - length; i >= 0; i--, string++)
14636       if (!strncmp(match, string, length))
14637         return string;
14638
14639     return NULL;
14640 }
14641
14642 char *
14643 StrCaseStr(string, match)
14644      char *string, *match;
14645 {
14646     int i, j, length;
14647
14648     length = strlen(match);
14649
14650     for (i = strlen(string) - length; i >= 0; i--, string++) {
14651         for (j = 0; j < length; j++) {
14652             if (ToLower(match[j]) != ToLower(string[j]))
14653               break;
14654         }
14655         if (j == length) return string;
14656     }
14657
14658     return NULL;
14659 }
14660
14661 #ifndef _amigados
14662 int
14663 StrCaseCmp(s1, s2)
14664      char *s1, *s2;
14665 {
14666     char c1, c2;
14667
14668     for (;;) {
14669         c1 = ToLower(*s1++);
14670         c2 = ToLower(*s2++);
14671         if (c1 > c2) return 1;
14672         if (c1 < c2) return -1;
14673         if (c1 == NULLCHAR) return 0;
14674     }
14675 }
14676
14677
14678 int
14679 ToLower(c)
14680      int c;
14681 {
14682     return isupper(c) ? tolower(c) : c;
14683 }
14684
14685
14686 int
14687 ToUpper(c)
14688      int c;
14689 {
14690     return islower(c) ? toupper(c) : c;
14691 }
14692 #endif /* !_amigados    */
14693
14694 char *
14695 StrSave(s)
14696      char *s;
14697 {
14698   char *ret;
14699
14700   if ((ret = (char *) malloc(strlen(s) + 1)))
14701     {
14702       safeStrCpy(ret, s, strlen(s)+1);
14703     }
14704   return ret;
14705 }
14706
14707 char *
14708 StrSavePtr(s, savePtr)
14709      char *s, **savePtr;
14710 {
14711     if (*savePtr) {
14712         free(*savePtr);
14713     }
14714     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14715       safeStrCpy(*savePtr, s, strlen(s)+1);
14716     }
14717     return(*savePtr);
14718 }
14719
14720 char *
14721 PGNDate()
14722 {
14723     time_t clock;
14724     struct tm *tm;
14725     char buf[MSG_SIZ];
14726
14727     clock = time((time_t *)NULL);
14728     tm = localtime(&clock);
14729     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14730             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14731     return StrSave(buf);
14732 }
14733
14734
14735 char *
14736 PositionToFEN(move, overrideCastling)
14737      int move;
14738      char *overrideCastling;
14739 {
14740     int i, j, fromX, fromY, toX, toY;
14741     int whiteToPlay;
14742     char buf[128];
14743     char *p, *q;
14744     int emptycount;
14745     ChessSquare piece;
14746
14747     whiteToPlay = (gameMode == EditPosition) ?
14748       !blackPlaysFirst : (move % 2 == 0);
14749     p = buf;
14750
14751     /* Piece placement data */
14752     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14753         emptycount = 0;
14754         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14755             if (boards[move][i][j] == EmptySquare) {
14756                 emptycount++;
14757             } else { ChessSquare piece = boards[move][i][j];
14758                 if (emptycount > 0) {
14759                     if(emptycount<10) /* [HGM] can be >= 10 */
14760                         *p++ = '0' + emptycount;
14761                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14762                     emptycount = 0;
14763                 }
14764                 if(PieceToChar(piece) == '+') {
14765                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14766                     *p++ = '+';
14767                     piece = (ChessSquare)(DEMOTED piece);
14768                 }
14769                 *p++ = PieceToChar(piece);
14770                 if(p[-1] == '~') {
14771                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14772                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14773                     *p++ = '~';
14774                 }
14775             }
14776         }
14777         if (emptycount > 0) {
14778             if(emptycount<10) /* [HGM] can be >= 10 */
14779                 *p++ = '0' + emptycount;
14780             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14781             emptycount = 0;
14782         }
14783         *p++ = '/';
14784     }
14785     *(p - 1) = ' ';
14786
14787     /* [HGM] print Crazyhouse or Shogi holdings */
14788     if( gameInfo.holdingsWidth ) {
14789         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14790         q = p;
14791         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14792             piece = boards[move][i][BOARD_WIDTH-1];
14793             if( piece != EmptySquare )
14794               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14795                   *p++ = PieceToChar(piece);
14796         }
14797         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14798             piece = boards[move][BOARD_HEIGHT-i-1][0];
14799             if( piece != EmptySquare )
14800               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14801                   *p++ = PieceToChar(piece);
14802         }
14803
14804         if( q == p ) *p++ = '-';
14805         *p++ = ']';
14806         *p++ = ' ';
14807     }
14808
14809     /* Active color */
14810     *p++ = whiteToPlay ? 'w' : 'b';
14811     *p++ = ' ';
14812
14813   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14814     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14815   } else {
14816   if(nrCastlingRights) {
14817      q = p;
14818      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14819        /* [HGM] write directly from rights */
14820            if(boards[move][CASTLING][2] != NoRights &&
14821               boards[move][CASTLING][0] != NoRights   )
14822                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14823            if(boards[move][CASTLING][2] != NoRights &&
14824               boards[move][CASTLING][1] != NoRights   )
14825                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14826            if(boards[move][CASTLING][5] != NoRights &&
14827               boards[move][CASTLING][3] != NoRights   )
14828                 *p++ = boards[move][CASTLING][3] + AAA;
14829            if(boards[move][CASTLING][5] != NoRights &&
14830               boards[move][CASTLING][4] != NoRights   )
14831                 *p++ = boards[move][CASTLING][4] + AAA;
14832      } else {
14833
14834         /* [HGM] write true castling rights */
14835         if( nrCastlingRights == 6 ) {
14836             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14837                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14838             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14839                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14840             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14841                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14842             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14843                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14844         }
14845      }
14846      if (q == p) *p++ = '-'; /* No castling rights */
14847      *p++ = ' ';
14848   }
14849
14850   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14851      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14852     /* En passant target square */
14853     if (move > backwardMostMove) {
14854         fromX = moveList[move - 1][0] - AAA;
14855         fromY = moveList[move - 1][1] - ONE;
14856         toX = moveList[move - 1][2] - AAA;
14857         toY = moveList[move - 1][3] - ONE;
14858         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14859             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14860             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14861             fromX == toX) {
14862             /* 2-square pawn move just happened */
14863             *p++ = toX + AAA;
14864             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14865         } else {
14866             *p++ = '-';
14867         }
14868     } else if(move == backwardMostMove) {
14869         // [HGM] perhaps we should always do it like this, and forget the above?
14870         if((signed char)boards[move][EP_STATUS] >= 0) {
14871             *p++ = boards[move][EP_STATUS] + AAA;
14872             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14873         } else {
14874             *p++ = '-';
14875         }
14876     } else {
14877         *p++ = '-';
14878     }
14879     *p++ = ' ';
14880   }
14881   }
14882
14883     /* [HGM] find reversible plies */
14884     {   int i = 0, j=move;
14885
14886         if (appData.debugMode) { int k;
14887             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14888             for(k=backwardMostMove; k<=forwardMostMove; k++)
14889                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14890
14891         }
14892
14893         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14894         if( j == backwardMostMove ) i += initialRulePlies;
14895         sprintf(p, "%d ", i);
14896         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14897     }
14898     /* Fullmove number */
14899     sprintf(p, "%d", (move / 2) + 1);
14900
14901     return StrSave(buf);
14902 }
14903
14904 Boolean
14905 ParseFEN(board, blackPlaysFirst, fen)
14906     Board board;
14907      int *blackPlaysFirst;
14908      char *fen;
14909 {
14910     int i, j;
14911     char *p, c;
14912     int emptycount;
14913     ChessSquare piece;
14914
14915     p = fen;
14916
14917     /* [HGM] by default clear Crazyhouse holdings, if present */
14918     if(gameInfo.holdingsWidth) {
14919        for(i=0; i<BOARD_HEIGHT; i++) {
14920            board[i][0]             = EmptySquare; /* black holdings */
14921            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14922            board[i][1]             = (ChessSquare) 0; /* black counts */
14923            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14924        }
14925     }
14926
14927     /* Piece placement data */
14928     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14929         j = 0;
14930         for (;;) {
14931             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14932                 if (*p == '/') p++;
14933                 emptycount = gameInfo.boardWidth - j;
14934                 while (emptycount--)
14935                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14936                 break;
14937 #if(BOARD_FILES >= 10)
14938             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14939                 p++; emptycount=10;
14940                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14941                 while (emptycount--)
14942                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14943 #endif
14944             } else if (isdigit(*p)) {
14945                 emptycount = *p++ - '0';
14946                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14947                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14948                 while (emptycount--)
14949                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14950             } else if (*p == '+' || isalpha(*p)) {
14951                 if (j >= gameInfo.boardWidth) return FALSE;
14952                 if(*p=='+') {
14953                     piece = CharToPiece(*++p);
14954                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14955                     piece = (ChessSquare) (PROMOTED piece ); p++;
14956                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14957                 } else piece = CharToPiece(*p++);
14958
14959                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14960                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14961                     piece = (ChessSquare) (PROMOTED piece);
14962                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14963                     p++;
14964                 }
14965                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14966             } else {
14967                 return FALSE;
14968             }
14969         }
14970     }
14971     while (*p == '/' || *p == ' ') p++;
14972
14973     /* [HGM] look for Crazyhouse holdings here */
14974     while(*p==' ') p++;
14975     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14976         if(*p == '[') p++;
14977         if(*p == '-' ) p++; /* empty holdings */ else {
14978             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14979             /* if we would allow FEN reading to set board size, we would   */
14980             /* have to add holdings and shift the board read so far here   */
14981             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14982                 p++;
14983                 if((int) piece >= (int) BlackPawn ) {
14984                     i = (int)piece - (int)BlackPawn;
14985                     i = PieceToNumber((ChessSquare)i);
14986                     if( i >= gameInfo.holdingsSize ) return FALSE;
14987                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14988                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14989                 } else {
14990                     i = (int)piece - (int)WhitePawn;
14991                     i = PieceToNumber((ChessSquare)i);
14992                     if( i >= gameInfo.holdingsSize ) return FALSE;
14993                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14994                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14995                 }
14996             }
14997         }
14998         if(*p == ']') p++;
14999     }
15000
15001     while(*p == ' ') p++;
15002
15003     /* Active color */
15004     c = *p++;
15005     if(appData.colorNickNames) {
15006       if( c == appData.colorNickNames[0] ) c = 'w'; else
15007       if( c == appData.colorNickNames[1] ) c = 'b';
15008     }
15009     switch (c) {
15010       case 'w':
15011         *blackPlaysFirst = FALSE;
15012         break;
15013       case 'b':
15014         *blackPlaysFirst = TRUE;
15015         break;
15016       default:
15017         return FALSE;
15018     }
15019
15020     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15021     /* return the extra info in global variiables             */
15022
15023     /* set defaults in case FEN is incomplete */
15024     board[EP_STATUS] = EP_UNKNOWN;
15025     for(i=0; i<nrCastlingRights; i++ ) {
15026         board[CASTLING][i] =
15027             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15028     }   /* assume possible unless obviously impossible */
15029     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15030     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15031     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15032                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15033     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15034     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15035     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15036                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15037     FENrulePlies = 0;
15038
15039     while(*p==' ') p++;
15040     if(nrCastlingRights) {
15041       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15042           /* castling indicator present, so default becomes no castlings */
15043           for(i=0; i<nrCastlingRights; i++ ) {
15044                  board[CASTLING][i] = NoRights;
15045           }
15046       }
15047       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15048              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15049              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15050              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15051         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15052
15053         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15054             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15055             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15056         }
15057         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15058             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15059         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15060                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15061         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15062                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15063         switch(c) {
15064           case'K':
15065               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15066               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15067               board[CASTLING][2] = whiteKingFile;
15068               break;
15069           case'Q':
15070               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15071               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15072               board[CASTLING][2] = whiteKingFile;
15073               break;
15074           case'k':
15075               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15076               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15077               board[CASTLING][5] = blackKingFile;
15078               break;
15079           case'q':
15080               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15081               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15082               board[CASTLING][5] = blackKingFile;
15083           case '-':
15084               break;
15085           default: /* FRC castlings */
15086               if(c >= 'a') { /* black rights */
15087                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15088                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15089                   if(i == BOARD_RGHT) break;
15090                   board[CASTLING][5] = i;
15091                   c -= AAA;
15092                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15093                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15094                   if(c > i)
15095                       board[CASTLING][3] = c;
15096                   else
15097                       board[CASTLING][4] = c;
15098               } else { /* white rights */
15099                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15100                     if(board[0][i] == WhiteKing) break;
15101                   if(i == BOARD_RGHT) break;
15102                   board[CASTLING][2] = i;
15103                   c -= AAA - 'a' + 'A';
15104                   if(board[0][c] >= WhiteKing) break;
15105                   if(c > i)
15106                       board[CASTLING][0] = c;
15107                   else
15108                       board[CASTLING][1] = c;
15109               }
15110         }
15111       }
15112       for(i=0; i<nrCastlingRights; i++)
15113         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15114     if (appData.debugMode) {
15115         fprintf(debugFP, "FEN castling rights:");
15116         for(i=0; i<nrCastlingRights; i++)
15117         fprintf(debugFP, " %d", board[CASTLING][i]);
15118         fprintf(debugFP, "\n");
15119     }
15120
15121       while(*p==' ') p++;
15122     }
15123
15124     /* read e.p. field in games that know e.p. capture */
15125     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15126        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15127       if(*p=='-') {
15128         p++; board[EP_STATUS] = EP_NONE;
15129       } else {
15130          char c = *p++ - AAA;
15131
15132          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15133          if(*p >= '0' && *p <='9') p++;
15134          board[EP_STATUS] = c;
15135       }
15136     }
15137
15138
15139     if(sscanf(p, "%d", &i) == 1) {
15140         FENrulePlies = i; /* 50-move ply counter */
15141         /* (The move number is still ignored)    */
15142     }
15143
15144     return TRUE;
15145 }
15146
15147 void
15148 EditPositionPasteFEN(char *fen)
15149 {
15150   if (fen != NULL) {
15151     Board initial_position;
15152
15153     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15154       DisplayError(_("Bad FEN position in clipboard"), 0);
15155       return ;
15156     } else {
15157       int savedBlackPlaysFirst = blackPlaysFirst;
15158       EditPositionEvent();
15159       blackPlaysFirst = savedBlackPlaysFirst;
15160       CopyBoard(boards[0], initial_position);
15161       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15162       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15163       DisplayBothClocks();
15164       DrawPosition(FALSE, boards[currentMove]);
15165     }
15166   }
15167 }
15168
15169 static char cseq[12] = "\\   ";
15170
15171 Boolean set_cont_sequence(char *new_seq)
15172 {
15173     int len;
15174     Boolean ret;
15175
15176     // handle bad attempts to set the sequence
15177         if (!new_seq)
15178                 return 0; // acceptable error - no debug
15179
15180     len = strlen(new_seq);
15181     ret = (len > 0) && (len < sizeof(cseq));
15182     if (ret)
15183       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15184     else if (appData.debugMode)
15185       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15186     return ret;
15187 }
15188
15189 /*
15190     reformat a source message so words don't cross the width boundary.  internal
15191     newlines are not removed.  returns the wrapped size (no null character unless
15192     included in source message).  If dest is NULL, only calculate the size required
15193     for the dest buffer.  lp argument indicats line position upon entry, and it's
15194     passed back upon exit.
15195 */
15196 int wrap(char *dest, char *src, int count, int width, int *lp)
15197 {
15198     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15199
15200     cseq_len = strlen(cseq);
15201     old_line = line = *lp;
15202     ansi = len = clen = 0;
15203
15204     for (i=0; i < count; i++)
15205     {
15206         if (src[i] == '\033')
15207             ansi = 1;
15208
15209         // if we hit the width, back up
15210         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15211         {
15212             // store i & len in case the word is too long
15213             old_i = i, old_len = len;
15214
15215             // find the end of the last word
15216             while (i && src[i] != ' ' && src[i] != '\n')
15217             {
15218                 i--;
15219                 len--;
15220             }
15221
15222             // word too long?  restore i & len before splitting it
15223             if ((old_i-i+clen) >= width)
15224             {
15225                 i = old_i;
15226                 len = old_len;
15227             }
15228
15229             // extra space?
15230             if (i && src[i-1] == ' ')
15231                 len--;
15232
15233             if (src[i] != ' ' && src[i] != '\n')
15234             {
15235                 i--;
15236                 if (len)
15237                     len--;
15238             }
15239
15240             // now append the newline and continuation sequence
15241             if (dest)
15242                 dest[len] = '\n';
15243             len++;
15244             if (dest)
15245                 strncpy(dest+len, cseq, cseq_len);
15246             len += cseq_len;
15247             line = cseq_len;
15248             clen = cseq_len;
15249             continue;
15250         }
15251
15252         if (dest)
15253             dest[len] = src[i];
15254         len++;
15255         if (!ansi)
15256             line++;
15257         if (src[i] == '\n')
15258             line = 0;
15259         if (src[i] == 'm')
15260             ansi = 0;
15261     }
15262     if (dest && appData.debugMode)
15263     {
15264         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15265             count, width, line, len, *lp);
15266         show_bytes(debugFP, src, count);
15267         fprintf(debugFP, "\ndest: ");
15268         show_bytes(debugFP, dest, len);
15269         fprintf(debugFP, "\n");
15270     }
15271     *lp = dest ? line : old_line;
15272
15273     return len;
15274 }
15275
15276 // [HGM] vari: routines for shelving variations
15277
15278 void
15279 PushTail(int firstMove, int lastMove)
15280 {
15281         int i, j, nrMoves = lastMove - firstMove;
15282
15283         if(appData.icsActive) { // only in local mode
15284                 forwardMostMove = currentMove; // mimic old ICS behavior
15285                 return;
15286         }
15287         if(storedGames >= MAX_VARIATIONS-1) return;
15288
15289         // push current tail of game on stack
15290         savedResult[storedGames] = gameInfo.result;
15291         savedDetails[storedGames] = gameInfo.resultDetails;
15292         gameInfo.resultDetails = NULL;
15293         savedFirst[storedGames] = firstMove;
15294         savedLast [storedGames] = lastMove;
15295         savedFramePtr[storedGames] = framePtr;
15296         framePtr -= nrMoves; // reserve space for the boards
15297         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15298             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15299             for(j=0; j<MOVE_LEN; j++)
15300                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15301             for(j=0; j<2*MOVE_LEN; j++)
15302                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15303             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15304             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15305             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15306             pvInfoList[firstMove+i-1].depth = 0;
15307             commentList[framePtr+i] = commentList[firstMove+i];
15308             commentList[firstMove+i] = NULL;
15309         }
15310
15311         storedGames++;
15312         forwardMostMove = firstMove; // truncate game so we can start variation
15313         if(storedGames == 1) GreyRevert(FALSE);
15314 }
15315
15316 Boolean
15317 PopTail(Boolean annotate)
15318 {
15319         int i, j, nrMoves;
15320         char buf[8000], moveBuf[20];
15321
15322         if(appData.icsActive) return FALSE; // only in local mode
15323         if(!storedGames) return FALSE; // sanity
15324         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15325
15326         storedGames--;
15327         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15328         nrMoves = savedLast[storedGames] - currentMove;
15329         if(annotate) {
15330                 int cnt = 10;
15331                 if(!WhiteOnMove(currentMove))
15332                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15333                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15334                 for(i=currentMove; i<forwardMostMove; i++) {
15335                         if(WhiteOnMove(i))
15336                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15337                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15338                         strcat(buf, moveBuf);
15339                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15340                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15341                 }
15342                 strcat(buf, ")");
15343         }
15344         for(i=1; i<=nrMoves; i++) { // copy last variation back
15345             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15346             for(j=0; j<MOVE_LEN; j++)
15347                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15348             for(j=0; j<2*MOVE_LEN; j++)
15349                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15350             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15351             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15352             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15353             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15354             commentList[currentMove+i] = commentList[framePtr+i];
15355             commentList[framePtr+i] = NULL;
15356         }
15357         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15358         framePtr = savedFramePtr[storedGames];
15359         gameInfo.result = savedResult[storedGames];
15360         if(gameInfo.resultDetails != NULL) {
15361             free(gameInfo.resultDetails);
15362       }
15363         gameInfo.resultDetails = savedDetails[storedGames];
15364         forwardMostMove = currentMove + nrMoves;
15365         if(storedGames == 0) GreyRevert(TRUE);
15366         return TRUE;
15367 }
15368
15369 void
15370 CleanupTail()
15371 {       // remove all shelved variations
15372         int i;
15373         for(i=0; i<storedGames; i++) {
15374             if(savedDetails[i])
15375                 free(savedDetails[i]);
15376             savedDetails[i] = NULL;
15377         }
15378         for(i=framePtr; i<MAX_MOVES; i++) {
15379                 if(commentList[i]) free(commentList[i]);
15380                 commentList[i] = NULL;
15381         }
15382         framePtr = MAX_MOVES-1;
15383         storedGames = 0;
15384 }
15385
15386 void
15387 LoadVariation(int index, char *text)
15388 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15389         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15390         int level = 0, move;
15391
15392         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15393         // first find outermost bracketing variation
15394         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15395             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15396                 if(*p == '{') wait = '}'; else
15397                 if(*p == '[') wait = ']'; else
15398                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15399                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15400             }
15401             if(*p == wait) wait = NULLCHAR; // closing ]} found
15402             p++;
15403         }
15404         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15405         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15406         end[1] = NULLCHAR; // clip off comment beyond variation
15407         ToNrEvent(currentMove-1);
15408         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15409         // kludge: use ParsePV() to append variation to game
15410         move = currentMove;
15411         ParsePV(start, TRUE);
15412         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15413         ClearPremoveHighlights();
15414         CommentPopDown();
15415         ToNrEvent(currentMove+1);
15416 }
15417