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