Implement entering gating moves with mouse
[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       case VariantSChess:     /* S-Chess, should work */
961         break;
962       }
963     }
964
965     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
966     InitEngineUCI( installDir, &second );
967 }
968
969 int NextIntegerFromString( char ** str, long * value )
970 {
971     int result = -1;
972     char * s = *str;
973
974     while( *s == ' ' || *s == '\t' ) {
975         s++;
976     }
977
978     *value = 0;
979
980     if( *s >= '0' && *s <= '9' ) {
981         while( *s >= '0' && *s <= '9' ) {
982             *value = *value * 10 + (*s - '0');
983             s++;
984         }
985
986         result = 0;
987     }
988
989     *str = s;
990
991     return result;
992 }
993
994 int NextTimeControlFromString( char ** str, long * value )
995 {
996     long temp;
997     int result = NextIntegerFromString( str, &temp );
998
999     if( result == 0 ) {
1000         *value = temp * 60; /* Minutes */
1001         if( **str == ':' ) {
1002             (*str)++;
1003             result = NextIntegerFromString( str, &temp );
1004             *value += temp; /* Seconds */
1005         }
1006     }
1007
1008     return result;
1009 }
1010
1011 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1012 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1013     int result = -1, type = 0; long temp, temp2;
1014
1015     if(**str != ':') return -1; // old params remain in force!
1016     (*str)++;
1017     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1018     if( NextIntegerFromString( str, &temp ) ) return -1;
1019     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1020
1021     if(**str != '/') {
1022         /* time only: incremental or sudden-death time control */
1023         if(**str == '+') { /* increment follows; read it */
1024             (*str)++;
1025             if(**str == '!') type = *(*str)++; // Bronstein TC
1026             if(result = NextIntegerFromString( str, &temp2)) return -1;
1027             *inc = temp2 * 1000;
1028             if(**str == '.') { // read fraction of increment
1029                 char *start = ++(*str);
1030                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1031                 temp2 *= 1000;
1032                 while(start++ < *str) temp2 /= 10;
1033                 *inc += temp2;
1034             }
1035         } else *inc = 0;
1036         *moves = 0; *tc = temp * 1000; *incType = type;
1037         return 0;
1038     }
1039
1040     (*str)++; /* classical time control */
1041     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1042
1043     if(result == 0) {
1044         *moves = temp;
1045         *tc    = temp2 * 1000;
1046         *inc   = 0;
1047         *incType = type;
1048     }
1049     return result;
1050 }
1051
1052 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1053 {   /* [HGM] get time to add from the multi-session time-control string */
1054     int incType, moves=1; /* kludge to force reading of first session */
1055     long time, increment;
1056     char *s = tcString;
1057
1058     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1059     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1060     do {
1061         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1062         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1063         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1064         if(movenr == -1) return time;    /* last move before new session     */
1065         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1066         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1067         if(!moves) return increment;     /* current session is incremental   */
1068         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1069     } while(movenr >= -1);               /* try again for next session       */
1070
1071     return 0; // no new time quota on this move
1072 }
1073
1074 int
1075 ParseTimeControl(tc, ti, mps)
1076      char *tc;
1077      float ti;
1078      int mps;
1079 {
1080   long tc1;
1081   long tc2;
1082   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1083   int min, sec=0;
1084
1085   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1086   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1087       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1088   if(ti > 0) {
1089
1090     if(mps)
1091       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1092     else
1093       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1094   } else {
1095     if(mps)
1096       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1097     else
1098       snprintf(buf, MSG_SIZ, ":%s", mytc);
1099   }
1100   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1101
1102   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1103     return FALSE;
1104   }
1105
1106   if( *tc == '/' ) {
1107     /* Parse second time control */
1108     tc++;
1109
1110     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1111       return FALSE;
1112     }
1113
1114     if( tc2 == 0 ) {
1115       return FALSE;
1116     }
1117
1118     timeControl_2 = tc2 * 1000;
1119   }
1120   else {
1121     timeControl_2 = 0;
1122   }
1123
1124   if( tc1 == 0 ) {
1125     return FALSE;
1126   }
1127
1128   timeControl = tc1 * 1000;
1129
1130   if (ti >= 0) {
1131     timeIncrement = ti * 1000;  /* convert to ms */
1132     movesPerSession = 0;
1133   } else {
1134     timeIncrement = 0;
1135     movesPerSession = mps;
1136   }
1137   return TRUE;
1138 }
1139
1140 void
1141 InitBackEnd2()
1142 {
1143     if (appData.debugMode) {
1144         fprintf(debugFP, "%s\n", programVersion);
1145     }
1146
1147     set_cont_sequence(appData.wrapContSeq);
1148     if (appData.matchGames > 0) {
1149         appData.matchMode = TRUE;
1150     } else if (appData.matchMode) {
1151         appData.matchGames = 1;
1152     }
1153     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1154         appData.matchGames = appData.sameColorGames;
1155     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1156         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1157         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1158     }
1159     Reset(TRUE, FALSE);
1160     if (appData.noChessProgram || first.protocolVersion == 1) {
1161       InitBackEnd3();
1162     } else {
1163       /* kludge: allow timeout for initial "feature" commands */
1164       FreezeUI();
1165       DisplayMessage("", _("Starting chess program"));
1166       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1167     }
1168 }
1169
1170 void
1171 InitBackEnd3 P((void))
1172 {
1173     GameMode initialMode;
1174     char buf[MSG_SIZ];
1175     int err, len;
1176
1177     InitChessProgram(&first, startedFromSetupPosition);
1178
1179     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1180         free(programVersion);
1181         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1182         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1183     }
1184
1185     if (appData.icsActive) {
1186 #ifdef WIN32
1187         /* [DM] Make a console window if needed [HGM] merged ifs */
1188         ConsoleCreate();
1189 #endif
1190         err = establish();
1191         if (err != 0)
1192           {
1193             if (*appData.icsCommPort != NULLCHAR)
1194               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1195                              appData.icsCommPort);
1196             else
1197               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1198                         appData.icsHost, appData.icsPort);
1199
1200             if( (len > MSG_SIZ) && appData.debugMode )
1201               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1202
1203             DisplayFatalError(buf, err, 1);
1204             return;
1205         }
1206         SetICSMode();
1207         telnetISR =
1208           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1209         fromUserISR =
1210           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1211         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1212             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1213     } else if (appData.noChessProgram) {
1214         SetNCPMode();
1215     } else {
1216         SetGNUMode();
1217     }
1218
1219     if (*appData.cmailGameName != NULLCHAR) {
1220         SetCmailMode();
1221         OpenLoopback(&cmailPR);
1222         cmailISR =
1223           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1224     }
1225
1226     ThawUI();
1227     DisplayMessage("", "");
1228     if (StrCaseCmp(appData.initialMode, "") == 0) {
1229       initialMode = BeginningOfGame;
1230     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1231       initialMode = TwoMachinesPlay;
1232     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1233       initialMode = AnalyzeFile;
1234     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1235       initialMode = AnalyzeMode;
1236     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1237       initialMode = MachinePlaysWhite;
1238     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1239       initialMode = MachinePlaysBlack;
1240     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1241       initialMode = EditGame;
1242     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1243       initialMode = EditPosition;
1244     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1245       initialMode = Training;
1246     } else {
1247       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1248       if( (len > MSG_SIZ) && appData.debugMode )
1249         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1250
1251       DisplayFatalError(buf, 0, 2);
1252       return;
1253     }
1254
1255     if (appData.matchMode) {
1256         /* Set up machine vs. machine match */
1257         if (appData.noChessProgram) {
1258             DisplayFatalError(_("Can't have a match with no chess programs"),
1259                               0, 2);
1260             return;
1261         }
1262         matchMode = TRUE;
1263         matchGame = 1;
1264         if (*appData.loadGameFile != NULLCHAR) {
1265             int index = appData.loadGameIndex; // [HGM] autoinc
1266             if(index<0) lastIndex = index = 1;
1267             if (!LoadGameFromFile(appData.loadGameFile,
1268                                   index,
1269                                   appData.loadGameFile, FALSE)) {
1270                 DisplayFatalError(_("Bad game file"), 0, 1);
1271                 return;
1272             }
1273         } else if (*appData.loadPositionFile != NULLCHAR) {
1274             int index = appData.loadPositionIndex; // [HGM] autoinc
1275             if(index<0) lastIndex = index = 1;
1276             if (!LoadPositionFromFile(appData.loadPositionFile,
1277                                       index,
1278                                       appData.loadPositionFile)) {
1279                 DisplayFatalError(_("Bad position file"), 0, 1);
1280                 return;
1281             }
1282         }
1283         TwoMachinesEvent();
1284     } else if (*appData.cmailGameName != NULLCHAR) {
1285         /* Set up cmail mode */
1286         ReloadCmailMsgEvent(TRUE);
1287     } else {
1288         /* Set up other modes */
1289         if (initialMode == AnalyzeFile) {
1290           if (*appData.loadGameFile == NULLCHAR) {
1291             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1292             return;
1293           }
1294         }
1295         if (*appData.loadGameFile != NULLCHAR) {
1296             (void) LoadGameFromFile(appData.loadGameFile,
1297                                     appData.loadGameIndex,
1298                                     appData.loadGameFile, TRUE);
1299         } else if (*appData.loadPositionFile != NULLCHAR) {
1300             (void) LoadPositionFromFile(appData.loadPositionFile,
1301                                         appData.loadPositionIndex,
1302                                         appData.loadPositionFile);
1303             /* [HGM] try to make self-starting even after FEN load */
1304             /* to allow automatic setup of fairy variants with wtm */
1305             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1306                 gameMode = BeginningOfGame;
1307                 setboardSpoiledMachineBlack = 1;
1308             }
1309             /* [HGM] loadPos: make that every new game uses the setup */
1310             /* from file as long as we do not switch variant          */
1311             if(!blackPlaysFirst) {
1312                 startedFromPositionFile = TRUE;
1313                 CopyBoard(filePosition, boards[0]);
1314             }
1315         }
1316         if (initialMode == AnalyzeMode) {
1317           if (appData.noChessProgram) {
1318             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1319             return;
1320           }
1321           if (appData.icsActive) {
1322             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1323             return;
1324           }
1325           AnalyzeModeEvent();
1326         } else if (initialMode == AnalyzeFile) {
1327           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1328           ShowThinkingEvent();
1329           AnalyzeFileEvent();
1330           AnalysisPeriodicEvent(1);
1331         } else if (initialMode == MachinePlaysWhite) {
1332           if (appData.noChessProgram) {
1333             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1334                               0, 2);
1335             return;
1336           }
1337           if (appData.icsActive) {
1338             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1339                               0, 2);
1340             return;
1341           }
1342           MachineWhiteEvent();
1343         } else if (initialMode == MachinePlaysBlack) {
1344           if (appData.noChessProgram) {
1345             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1346                               0, 2);
1347             return;
1348           }
1349           if (appData.icsActive) {
1350             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1351                               0, 2);
1352             return;
1353           }
1354           MachineBlackEvent();
1355         } else if (initialMode == TwoMachinesPlay) {
1356           if (appData.noChessProgram) {
1357             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1358                               0, 2);
1359             return;
1360           }
1361           if (appData.icsActive) {
1362             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1363                               0, 2);
1364             return;
1365           }
1366           TwoMachinesEvent();
1367         } else if (initialMode == EditGame) {
1368           EditGameEvent();
1369         } else if (initialMode == EditPosition) {
1370           EditPositionEvent();
1371         } else if (initialMode == Training) {
1372           if (*appData.loadGameFile == NULLCHAR) {
1373             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1374             return;
1375           }
1376           TrainingEvent();
1377         }
1378     }
1379 }
1380
1381 /*
1382  * Establish will establish a contact to a remote host.port.
1383  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1384  *  used to talk to the host.
1385  * Returns 0 if okay, error code if not.
1386  */
1387 int
1388 establish()
1389 {
1390     char buf[MSG_SIZ];
1391
1392     if (*appData.icsCommPort != NULLCHAR) {
1393         /* Talk to the host through a serial comm port */
1394         return OpenCommPort(appData.icsCommPort, &icsPR);
1395
1396     } else if (*appData.gateway != NULLCHAR) {
1397         if (*appData.remoteShell == NULLCHAR) {
1398             /* Use the rcmd protocol to run telnet program on a gateway host */
1399             snprintf(buf, sizeof(buf), "%s %s %s",
1400                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1401             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1402
1403         } else {
1404             /* Use the rsh program to run telnet program on a gateway host */
1405             if (*appData.remoteUser == NULLCHAR) {
1406                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1407                         appData.gateway, appData.telnetProgram,
1408                         appData.icsHost, appData.icsPort);
1409             } else {
1410                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1411                         appData.remoteShell, appData.gateway,
1412                         appData.remoteUser, appData.telnetProgram,
1413                         appData.icsHost, appData.icsPort);
1414             }
1415             return StartChildProcess(buf, "", &icsPR);
1416
1417         }
1418     } else if (appData.useTelnet) {
1419         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1420
1421     } else {
1422         /* TCP socket interface differs somewhat between
1423            Unix and NT; handle details in the front end.
1424            */
1425         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1426     }
1427 }
1428
1429 void EscapeExpand(char *p, char *q)
1430 {       // [HGM] initstring: routine to shape up string arguments
1431         while(*p++ = *q++) if(p[-1] == '\\')
1432             switch(*q++) {
1433                 case 'n': p[-1] = '\n'; break;
1434                 case 'r': p[-1] = '\r'; break;
1435                 case 't': p[-1] = '\t'; break;
1436                 case '\\': p[-1] = '\\'; break;
1437                 case 0: *p = 0; return;
1438                 default: p[-1] = q[-1]; break;
1439             }
1440 }
1441
1442 void
1443 show_bytes(fp, buf, count)
1444      FILE *fp;
1445      char *buf;
1446      int count;
1447 {
1448     while (count--) {
1449         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1450             fprintf(fp, "\\%03o", *buf & 0xff);
1451         } else {
1452             putc(*buf, fp);
1453         }
1454         buf++;
1455     }
1456     fflush(fp);
1457 }
1458
1459 /* Returns an errno value */
1460 int
1461 OutputMaybeTelnet(pr, message, count, outError)
1462      ProcRef pr;
1463      char *message;
1464      int count;
1465      int *outError;
1466 {
1467     char buf[8192], *p, *q, *buflim;
1468     int left, newcount, outcount;
1469
1470     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1471         *appData.gateway != NULLCHAR) {
1472         if (appData.debugMode) {
1473             fprintf(debugFP, ">ICS: ");
1474             show_bytes(debugFP, message, count);
1475             fprintf(debugFP, "\n");
1476         }
1477         return OutputToProcess(pr, message, count, outError);
1478     }
1479
1480     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1481     p = message;
1482     q = buf;
1483     left = count;
1484     newcount = 0;
1485     while (left) {
1486         if (q >= buflim) {
1487             if (appData.debugMode) {
1488                 fprintf(debugFP, ">ICS: ");
1489                 show_bytes(debugFP, buf, newcount);
1490                 fprintf(debugFP, "\n");
1491             }
1492             outcount = OutputToProcess(pr, buf, newcount, outError);
1493             if (outcount < newcount) return -1; /* to be sure */
1494             q = buf;
1495             newcount = 0;
1496         }
1497         if (*p == '\n') {
1498             *q++ = '\r';
1499             newcount++;
1500         } else if (((unsigned char) *p) == TN_IAC) {
1501             *q++ = (char) TN_IAC;
1502             newcount ++;
1503         }
1504         *q++ = *p++;
1505         newcount++;
1506         left--;
1507     }
1508     if (appData.debugMode) {
1509         fprintf(debugFP, ">ICS: ");
1510         show_bytes(debugFP, buf, newcount);
1511         fprintf(debugFP, "\n");
1512     }
1513     outcount = OutputToProcess(pr, buf, newcount, outError);
1514     if (outcount < newcount) return -1; /* to be sure */
1515     return count;
1516 }
1517
1518 void
1519 read_from_player(isr, closure, message, count, error)
1520      InputSourceRef isr;
1521      VOIDSTAR closure;
1522      char *message;
1523      int count;
1524      int error;
1525 {
1526     int outError, outCount;
1527     static int gotEof = 0;
1528
1529     /* Pass data read from player on to ICS */
1530     if (count > 0) {
1531         gotEof = 0;
1532         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1533         if (outCount < count) {
1534             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1535         }
1536     } else if (count < 0) {
1537         RemoveInputSource(isr);
1538         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1539     } else if (gotEof++ > 0) {
1540         RemoveInputSource(isr);
1541         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1542     }
1543 }
1544
1545 void
1546 KeepAlive()
1547 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1548     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1549     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1550     SendToICS("date\n");
1551     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1552 }
1553
1554 /* added routine for printf style output to ics */
1555 void ics_printf(char *format, ...)
1556 {
1557     char buffer[MSG_SIZ];
1558     va_list args;
1559
1560     va_start(args, format);
1561     vsnprintf(buffer, sizeof(buffer), format, args);
1562     buffer[sizeof(buffer)-1] = '\0';
1563     SendToICS(buffer);
1564     va_end(args);
1565 }
1566
1567 void
1568 SendToICS(s)
1569      char *s;
1570 {
1571     int count, outCount, outError;
1572
1573     if (icsPR == NULL) return;
1574
1575     count = strlen(s);
1576     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1577     if (outCount < count) {
1578         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1579     }
1580 }
1581
1582 /* This is used for sending logon scripts to the ICS. Sending
1583    without a delay causes problems when using timestamp on ICC
1584    (at least on my machine). */
1585 void
1586 SendToICSDelayed(s,msdelay)
1587      char *s;
1588      long msdelay;
1589 {
1590     int count, outCount, outError;
1591
1592     if (icsPR == NULL) return;
1593
1594     count = strlen(s);
1595     if (appData.debugMode) {
1596         fprintf(debugFP, ">ICS: ");
1597         show_bytes(debugFP, s, count);
1598         fprintf(debugFP, "\n");
1599     }
1600     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1601                                       msdelay);
1602     if (outCount < count) {
1603         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1604     }
1605 }
1606
1607
1608 /* Remove all highlighting escape sequences in s
1609    Also deletes any suffix starting with '('
1610    */
1611 char *
1612 StripHighlightAndTitle(s)
1613      char *s;
1614 {
1615     static char retbuf[MSG_SIZ];
1616     char *p = retbuf;
1617
1618     while (*s != NULLCHAR) {
1619         while (*s == '\033') {
1620             while (*s != NULLCHAR && !isalpha(*s)) s++;
1621             if (*s != NULLCHAR) s++;
1622         }
1623         while (*s != NULLCHAR && *s != '\033') {
1624             if (*s == '(' || *s == '[') {
1625                 *p = NULLCHAR;
1626                 return retbuf;
1627             }
1628             *p++ = *s++;
1629         }
1630     }
1631     *p = NULLCHAR;
1632     return retbuf;
1633 }
1634
1635 /* Remove all highlighting escape sequences in s */
1636 char *
1637 StripHighlight(s)
1638      char *s;
1639 {
1640     static char retbuf[MSG_SIZ];
1641     char *p = retbuf;
1642
1643     while (*s != NULLCHAR) {
1644         while (*s == '\033') {
1645             while (*s != NULLCHAR && !isalpha(*s)) s++;
1646             if (*s != NULLCHAR) s++;
1647         }
1648         while (*s != NULLCHAR && *s != '\033') {
1649             *p++ = *s++;
1650         }
1651     }
1652     *p = NULLCHAR;
1653     return retbuf;
1654 }
1655
1656 char *variantNames[] = VARIANT_NAMES;
1657 char *
1658 VariantName(v)
1659      VariantClass v;
1660 {
1661     return variantNames[v];
1662 }
1663
1664
1665 /* Identify a variant from the strings the chess servers use or the
1666    PGN Variant tag names we use. */
1667 VariantClass
1668 StringToVariant(e)
1669      char *e;
1670 {
1671     char *p;
1672     int wnum = -1;
1673     VariantClass v = VariantNormal;
1674     int i, found = FALSE;
1675     char buf[MSG_SIZ];
1676     int len;
1677
1678     if (!e) return v;
1679
1680     /* [HGM] skip over optional board-size prefixes */
1681     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1682         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1683         while( *e++ != '_');
1684     }
1685
1686     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1687         v = VariantNormal;
1688         found = TRUE;
1689     } else
1690     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1691       if (StrCaseStr(e, variantNames[i])) {
1692         v = (VariantClass) i;
1693         found = TRUE;
1694         break;
1695       }
1696     }
1697
1698     if (!found) {
1699       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1700           || StrCaseStr(e, "wild/fr")
1701           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1702         v = VariantFischeRandom;
1703       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1704                  (i = 1, p = StrCaseStr(e, "w"))) {
1705         p += i;
1706         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1707         if (isdigit(*p)) {
1708           wnum = atoi(p);
1709         } else {
1710           wnum = -1;
1711         }
1712         switch (wnum) {
1713         case 0: /* FICS only, actually */
1714         case 1:
1715           /* Castling legal even if K starts on d-file */
1716           v = VariantWildCastle;
1717           break;
1718         case 2:
1719         case 3:
1720         case 4:
1721           /* Castling illegal even if K & R happen to start in
1722              normal positions. */
1723           v = VariantNoCastle;
1724           break;
1725         case 5:
1726         case 7:
1727         case 8:
1728         case 10:
1729         case 11:
1730         case 12:
1731         case 13:
1732         case 14:
1733         case 15:
1734         case 18:
1735         case 19:
1736           /* Castling legal iff K & R start in normal positions */
1737           v = VariantNormal;
1738           break;
1739         case 6:
1740         case 20:
1741         case 21:
1742           /* Special wilds for position setup; unclear what to do here */
1743           v = VariantLoadable;
1744           break;
1745         case 9:
1746           /* Bizarre ICC game */
1747           v = VariantTwoKings;
1748           break;
1749         case 16:
1750           v = VariantKriegspiel;
1751           break;
1752         case 17:
1753           v = VariantLosers;
1754           break;
1755         case 22:
1756           v = VariantFischeRandom;
1757           break;
1758         case 23:
1759           v = VariantCrazyhouse;
1760           break;
1761         case 24:
1762           v = VariantBughouse;
1763           break;
1764         case 25:
1765           v = Variant3Check;
1766           break;
1767         case 26:
1768           /* Not quite the same as FICS suicide! */
1769           v = VariantGiveaway;
1770           break;
1771         case 27:
1772           v = VariantAtomic;
1773           break;
1774         case 28:
1775           v = VariantShatranj;
1776           break;
1777
1778         /* Temporary names for future ICC types.  The name *will* change in
1779            the next xboard/WinBoard release after ICC defines it. */
1780         case 29:
1781           v = Variant29;
1782           break;
1783         case 30:
1784           v = Variant30;
1785           break;
1786         case 31:
1787           v = Variant31;
1788           break;
1789         case 32:
1790           v = Variant32;
1791           break;
1792         case 33:
1793           v = Variant33;
1794           break;
1795         case 34:
1796           v = Variant34;
1797           break;
1798         case 35:
1799           v = Variant35;
1800           break;
1801         case 36:
1802           v = Variant36;
1803           break;
1804         case 37:
1805           v = VariantShogi;
1806           break;
1807         case 38:
1808           v = VariantXiangqi;
1809           break;
1810         case 39:
1811           v = VariantCourier;
1812           break;
1813         case 40:
1814           v = VariantGothic;
1815           break;
1816         case 41:
1817           v = VariantCapablanca;
1818           break;
1819         case 42:
1820           v = VariantKnightmate;
1821           break;
1822         case 43:
1823           v = VariantFairy;
1824           break;
1825         case 44:
1826           v = VariantCylinder;
1827           break;
1828         case 45:
1829           v = VariantFalcon;
1830           break;
1831         case 46:
1832           v = VariantCapaRandom;
1833           break;
1834         case 47:
1835           v = VariantBerolina;
1836           break;
1837         case 48:
1838           v = VariantJanus;
1839           break;
1840         case 49:
1841           v = VariantSuper;
1842           break;
1843         case 50:
1844           v = VariantGreat;
1845           break;
1846         case -1:
1847           /* Found "wild" or "w" in the string but no number;
1848              must assume it's normal chess. */
1849           v = VariantNormal;
1850           break;
1851         default:
1852           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1853           if( (len > MSG_SIZ) && appData.debugMode )
1854             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1855
1856           DisplayError(buf, 0);
1857           v = VariantUnknown;
1858           break;
1859         }
1860       }
1861     }
1862     if (appData.debugMode) {
1863       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1864               e, wnum, VariantName(v));
1865     }
1866     return v;
1867 }
1868
1869 static int leftover_start = 0, leftover_len = 0;
1870 char star_match[STAR_MATCH_N][MSG_SIZ];
1871
1872 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1873    advance *index beyond it, and set leftover_start to the new value of
1874    *index; else return FALSE.  If pattern contains the character '*', it
1875    matches any sequence of characters not containing '\r', '\n', or the
1876    character following the '*' (if any), and the matched sequence(s) are
1877    copied into star_match.
1878    */
1879 int
1880 looking_at(buf, index, pattern)
1881      char *buf;
1882      int *index;
1883      char *pattern;
1884 {
1885     char *bufp = &buf[*index], *patternp = pattern;
1886     int star_count = 0;
1887     char *matchp = star_match[0];
1888
1889     for (;;) {
1890         if (*patternp == NULLCHAR) {
1891             *index = leftover_start = bufp - buf;
1892             *matchp = NULLCHAR;
1893             return TRUE;
1894         }
1895         if (*bufp == NULLCHAR) return FALSE;
1896         if (*patternp == '*') {
1897             if (*bufp == *(patternp + 1)) {
1898                 *matchp = NULLCHAR;
1899                 matchp = star_match[++star_count];
1900                 patternp += 2;
1901                 bufp++;
1902                 continue;
1903             } else if (*bufp == '\n' || *bufp == '\r') {
1904                 patternp++;
1905                 if (*patternp == NULLCHAR)
1906                   continue;
1907                 else
1908                   return FALSE;
1909             } else {
1910                 *matchp++ = *bufp++;
1911                 continue;
1912             }
1913         }
1914         if (*patternp != *bufp) return FALSE;
1915         patternp++;
1916         bufp++;
1917     }
1918 }
1919
1920 void
1921 SendToPlayer(data, length)
1922      char *data;
1923      int length;
1924 {
1925     int error, outCount;
1926     outCount = OutputToProcess(NoProc, data, length, &error);
1927     if (outCount < length) {
1928         DisplayFatalError(_("Error writing to display"), error, 1);
1929     }
1930 }
1931
1932 void
1933 PackHolding(packed, holding)
1934      char packed[];
1935      char *holding;
1936 {
1937     char *p = holding;
1938     char *q = packed;
1939     int runlength = 0;
1940     int curr = 9999;
1941     do {
1942         if (*p == curr) {
1943             runlength++;
1944         } else {
1945             switch (runlength) {
1946               case 0:
1947                 break;
1948               case 1:
1949                 *q++ = curr;
1950                 break;
1951               case 2:
1952                 *q++ = curr;
1953                 *q++ = curr;
1954                 break;
1955               default:
1956                 sprintf(q, "%d", runlength);
1957                 while (*q) q++;
1958                 *q++ = curr;
1959                 break;
1960             }
1961             runlength = 1;
1962             curr = *p;
1963         }
1964     } while (*p++);
1965     *q = NULLCHAR;
1966 }
1967
1968 /* Telnet protocol requests from the front end */
1969 void
1970 TelnetRequest(ddww, option)
1971      unsigned char ddww, option;
1972 {
1973     unsigned char msg[3];
1974     int outCount, outError;
1975
1976     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1977
1978     if (appData.debugMode) {
1979         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1980         switch (ddww) {
1981           case TN_DO:
1982             ddwwStr = "DO";
1983             break;
1984           case TN_DONT:
1985             ddwwStr = "DONT";
1986             break;
1987           case TN_WILL:
1988             ddwwStr = "WILL";
1989             break;
1990           case TN_WONT:
1991             ddwwStr = "WONT";
1992             break;
1993           default:
1994             ddwwStr = buf1;
1995             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1996             break;
1997         }
1998         switch (option) {
1999           case TN_ECHO:
2000             optionStr = "ECHO";
2001             break;
2002           default:
2003             optionStr = buf2;
2004             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2005             break;
2006         }
2007         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2008     }
2009     msg[0] = TN_IAC;
2010     msg[1] = ddww;
2011     msg[2] = option;
2012     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2013     if (outCount < 3) {
2014         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2015     }
2016 }
2017
2018 void
2019 DoEcho()
2020 {
2021     if (!appData.icsActive) return;
2022     TelnetRequest(TN_DO, TN_ECHO);
2023 }
2024
2025 void
2026 DontEcho()
2027 {
2028     if (!appData.icsActive) return;
2029     TelnetRequest(TN_DONT, TN_ECHO);
2030 }
2031
2032 void
2033 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2034 {
2035     /* put the holdings sent to us by the server on the board holdings area */
2036     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2037     char p;
2038     ChessSquare piece;
2039
2040     if(gameInfo.holdingsWidth < 2)  return;
2041     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2042         return; // prevent overwriting by pre-board holdings
2043
2044     if( (int)lowestPiece >= BlackPawn ) {
2045         holdingsColumn = 0;
2046         countsColumn = 1;
2047         holdingsStartRow = BOARD_HEIGHT-1;
2048         direction = -1;
2049     } else {
2050         holdingsColumn = BOARD_WIDTH-1;
2051         countsColumn = BOARD_WIDTH-2;
2052         holdingsStartRow = 0;
2053         direction = 1;
2054     }
2055
2056     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2057         board[i][holdingsColumn] = EmptySquare;
2058         board[i][countsColumn]   = (ChessSquare) 0;
2059     }
2060     while( (p=*holdings++) != NULLCHAR ) {
2061         piece = CharToPiece( ToUpper(p) );
2062         if(piece == EmptySquare) continue;
2063         /*j = (int) piece - (int) WhitePawn;*/
2064         j = PieceToNumber(piece);
2065         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2066         if(j < 0) continue;               /* should not happen */
2067         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2068         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2069         board[holdingsStartRow+j*direction][countsColumn]++;
2070     }
2071 }
2072
2073
2074 void
2075 VariantSwitch(Board board, VariantClass newVariant)
2076 {
2077    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2078    static Board oldBoard;
2079
2080    startedFromPositionFile = FALSE;
2081    if(gameInfo.variant == newVariant) return;
2082
2083    /* [HGM] This routine is called each time an assignment is made to
2084     * gameInfo.variant during a game, to make sure the board sizes
2085     * are set to match the new variant. If that means adding or deleting
2086     * holdings, we shift the playing board accordingly
2087     * This kludge is needed because in ICS observe mode, we get boards
2088     * of an ongoing game without knowing the variant, and learn about the
2089     * latter only later. This can be because of the move list we requested,
2090     * in which case the game history is refilled from the beginning anyway,
2091     * but also when receiving holdings of a crazyhouse game. In the latter
2092     * case we want to add those holdings to the already received position.
2093     */
2094
2095
2096    if (appData.debugMode) {
2097      fprintf(debugFP, "Switch board from %s to %s\n",
2098              VariantName(gameInfo.variant), VariantName(newVariant));
2099      setbuf(debugFP, NULL);
2100    }
2101    shuffleOpenings = 0;       /* [HGM] shuffle */
2102    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2103    switch(newVariant)
2104      {
2105      case VariantShogi:
2106        newWidth = 9;  newHeight = 9;
2107        gameInfo.holdingsSize = 7;
2108      case VariantBughouse:
2109      case VariantCrazyhouse:
2110        newHoldingsWidth = 2; break;
2111      case VariantGreat:
2112        newWidth = 10;
2113      case VariantSuper:
2114        newHoldingsWidth = 2;
2115        gameInfo.holdingsSize = 8;
2116        break;
2117      case VariantGothic:
2118      case VariantCapablanca:
2119      case VariantCapaRandom:
2120        newWidth = 10;
2121      default:
2122        newHoldingsWidth = gameInfo.holdingsSize = 0;
2123      };
2124
2125    if(newWidth  != gameInfo.boardWidth  ||
2126       newHeight != gameInfo.boardHeight ||
2127       newHoldingsWidth != gameInfo.holdingsWidth ) {
2128
2129      /* shift position to new playing area, if needed */
2130      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2131        for(i=0; i<BOARD_HEIGHT; i++)
2132          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2133            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2134              board[i][j];
2135        for(i=0; i<newHeight; i++) {
2136          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2137          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2138        }
2139      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2140        for(i=0; i<BOARD_HEIGHT; i++)
2141          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2142            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2143              board[i][j];
2144      }
2145      gameInfo.boardWidth  = newWidth;
2146      gameInfo.boardHeight = newHeight;
2147      gameInfo.holdingsWidth = newHoldingsWidth;
2148      gameInfo.variant = newVariant;
2149      InitDrawingSizes(-2, 0);
2150    } else gameInfo.variant = newVariant;
2151    CopyBoard(oldBoard, board);   // remember correctly formatted board
2152      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2153    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2154 }
2155
2156 static int loggedOn = FALSE;
2157
2158 /*-- Game start info cache: --*/
2159 int gs_gamenum;
2160 char gs_kind[MSG_SIZ];
2161 static char player1Name[128] = "";
2162 static char player2Name[128] = "";
2163 static char cont_seq[] = "\n\\   ";
2164 static int player1Rating = -1;
2165 static int player2Rating = -1;
2166 /*----------------------------*/
2167
2168 ColorClass curColor = ColorNormal;
2169 int suppressKibitz = 0;
2170
2171 // [HGM] seekgraph
2172 Boolean soughtPending = FALSE;
2173 Boolean seekGraphUp;
2174 #define MAX_SEEK_ADS 200
2175 #define SQUARE 0x80
2176 char *seekAdList[MAX_SEEK_ADS];
2177 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2178 float tcList[MAX_SEEK_ADS];
2179 char colorList[MAX_SEEK_ADS];
2180 int nrOfSeekAds = 0;
2181 int minRating = 1010, maxRating = 2800;
2182 int hMargin = 10, vMargin = 20, h, w;
2183 extern int squareSize, lineGap;
2184
2185 void
2186 PlotSeekAd(int i)
2187 {
2188         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2189         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2190         if(r < minRating+100 && r >=0 ) r = minRating+100;
2191         if(r > maxRating) r = maxRating;
2192         if(tc < 1.) tc = 1.;
2193         if(tc > 95.) tc = 95.;
2194         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2195         y = ((double)r - minRating)/(maxRating - minRating)
2196             * (h-vMargin-squareSize/8-1) + vMargin;
2197         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2198         if(strstr(seekAdList[i], " u ")) color = 1;
2199         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2200            !strstr(seekAdList[i], "bullet") &&
2201            !strstr(seekAdList[i], "blitz") &&
2202            !strstr(seekAdList[i], "standard") ) color = 2;
2203         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2204         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2205 }
2206
2207 void
2208 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2209 {
2210         char buf[MSG_SIZ], *ext = "";
2211         VariantClass v = StringToVariant(type);
2212         if(strstr(type, "wild")) {
2213             ext = type + 4; // append wild number
2214             if(v == VariantFischeRandom) type = "chess960"; else
2215             if(v == VariantLoadable) type = "setup"; else
2216             type = VariantName(v);
2217         }
2218         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2219         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2220             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2221             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2222             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2223             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2224             seekNrList[nrOfSeekAds] = nr;
2225             zList[nrOfSeekAds] = 0;
2226             seekAdList[nrOfSeekAds++] = StrSave(buf);
2227             if(plot) PlotSeekAd(nrOfSeekAds-1);
2228         }
2229 }
2230
2231 void
2232 EraseSeekDot(int i)
2233 {
2234     int x = xList[i], y = yList[i], d=squareSize/4, k;
2235     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2236     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2237     // now replot every dot that overlapped
2238     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2239         int xx = xList[k], yy = yList[k];
2240         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2241             DrawSeekDot(xx, yy, colorList[k]);
2242     }
2243 }
2244
2245 void
2246 RemoveSeekAd(int nr)
2247 {
2248         int i;
2249         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2250             EraseSeekDot(i);
2251             if(seekAdList[i]) free(seekAdList[i]);
2252             seekAdList[i] = seekAdList[--nrOfSeekAds];
2253             seekNrList[i] = seekNrList[nrOfSeekAds];
2254             ratingList[i] = ratingList[nrOfSeekAds];
2255             colorList[i]  = colorList[nrOfSeekAds];
2256             tcList[i] = tcList[nrOfSeekAds];
2257             xList[i]  = xList[nrOfSeekAds];
2258             yList[i]  = yList[nrOfSeekAds];
2259             zList[i]  = zList[nrOfSeekAds];
2260             seekAdList[nrOfSeekAds] = NULL;
2261             break;
2262         }
2263 }
2264
2265 Boolean
2266 MatchSoughtLine(char *line)
2267 {
2268     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2269     int nr, base, inc, u=0; char dummy;
2270
2271     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2272        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2273        (u=1) &&
2274        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2275         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2276         // match: compact and save the line
2277         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2278         return TRUE;
2279     }
2280     return FALSE;
2281 }
2282
2283 int
2284 DrawSeekGraph()
2285 {
2286     int i;
2287     if(!seekGraphUp) return FALSE;
2288     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2289     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2290
2291     DrawSeekBackground(0, 0, w, h);
2292     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2293     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2294     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2295         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2296         yy = h-1-yy;
2297         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2298         if(i%500 == 0) {
2299             char buf[MSG_SIZ];
2300             snprintf(buf, MSG_SIZ, "%d", i);
2301             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2302         }
2303     }
2304     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2305     for(i=1; i<100; i+=(i<10?1:5)) {
2306         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2307         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2308         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2309             char buf[MSG_SIZ];
2310             snprintf(buf, MSG_SIZ, "%d", i);
2311             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2312         }
2313     }
2314     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2315     return TRUE;
2316 }
2317
2318 int SeekGraphClick(ClickType click, int x, int y, int moving)
2319 {
2320     static int lastDown = 0, displayed = 0, lastSecond;
2321     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2322         if(click == Release || moving) return FALSE;
2323         nrOfSeekAds = 0;
2324         soughtPending = TRUE;
2325         SendToICS(ics_prefix);
2326         SendToICS("sought\n"); // should this be "sought all"?
2327     } else { // issue challenge based on clicked ad
2328         int dist = 10000; int i, closest = 0, second = 0;
2329         for(i=0; i<nrOfSeekAds; i++) {
2330             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2331             if(d < dist) { dist = d; closest = i; }
2332             second += (d - zList[i] < 120); // count in-range ads
2333             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2334         }
2335         if(dist < 120) {
2336             char buf[MSG_SIZ];
2337             second = (second > 1);
2338             if(displayed != closest || second != lastSecond) {
2339                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2340                 lastSecond = second; displayed = closest;
2341             }
2342             if(click == Press) {
2343                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2344                 lastDown = closest;
2345                 return TRUE;
2346             } // on press 'hit', only show info
2347             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2348             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2349             SendToICS(ics_prefix);
2350             SendToICS(buf);
2351             return TRUE; // let incoming board of started game pop down the graph
2352         } else if(click == Release) { // release 'miss' is ignored
2353             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2354             if(moving == 2) { // right up-click
2355                 nrOfSeekAds = 0; // refresh graph
2356                 soughtPending = TRUE;
2357                 SendToICS(ics_prefix);
2358                 SendToICS("sought\n"); // should this be "sought all"?
2359             }
2360             return TRUE;
2361         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2362         // press miss or release hit 'pop down' seek graph
2363         seekGraphUp = FALSE;
2364         DrawPosition(TRUE, NULL);
2365     }
2366     return TRUE;
2367 }
2368
2369 void
2370 read_from_ics(isr, closure, data, count, error)
2371      InputSourceRef isr;
2372      VOIDSTAR closure;
2373      char *data;
2374      int count;
2375      int error;
2376 {
2377 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2378 #define STARTED_NONE 0
2379 #define STARTED_MOVES 1
2380 #define STARTED_BOARD 2
2381 #define STARTED_OBSERVE 3
2382 #define STARTED_HOLDINGS 4
2383 #define STARTED_CHATTER 5
2384 #define STARTED_COMMENT 6
2385 #define STARTED_MOVES_NOHIDE 7
2386
2387     static int started = STARTED_NONE;
2388     static char parse[20000];
2389     static int parse_pos = 0;
2390     static char buf[BUF_SIZE + 1];
2391     static int firstTime = TRUE, intfSet = FALSE;
2392     static ColorClass prevColor = ColorNormal;
2393     static int savingComment = FALSE;
2394     static int cmatch = 0; // continuation sequence match
2395     char *bp;
2396     char str[MSG_SIZ];
2397     int i, oldi;
2398     int buf_len;
2399     int next_out;
2400     int tkind;
2401     int backup;    /* [DM] For zippy color lines */
2402     char *p;
2403     char talker[MSG_SIZ]; // [HGM] chat
2404     int channel;
2405
2406     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2407
2408     if (appData.debugMode) {
2409       if (!error) {
2410         fprintf(debugFP, "<ICS: ");
2411         show_bytes(debugFP, data, count);
2412         fprintf(debugFP, "\n");
2413       }
2414     }
2415
2416     if (appData.debugMode) { int f = forwardMostMove;
2417         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2418                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2419                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2420     }
2421     if (count > 0) {
2422         /* If last read ended with a partial line that we couldn't parse,
2423            prepend it to the new read and try again. */
2424         if (leftover_len > 0) {
2425             for (i=0; i<leftover_len; i++)
2426               buf[i] = buf[leftover_start + i];
2427         }
2428
2429     /* copy new characters into the buffer */
2430     bp = buf + leftover_len;
2431     buf_len=leftover_len;
2432     for (i=0; i<count; i++)
2433     {
2434         // ignore these
2435         if (data[i] == '\r')
2436             continue;
2437
2438         // join lines split by ICS?
2439         if (!appData.noJoin)
2440         {
2441             /*
2442                 Joining just consists of finding matches against the
2443                 continuation sequence, and discarding that sequence
2444                 if found instead of copying it.  So, until a match
2445                 fails, there's nothing to do since it might be the
2446                 complete sequence, and thus, something we don't want
2447                 copied.
2448             */
2449             if (data[i] == cont_seq[cmatch])
2450             {
2451                 cmatch++;
2452                 if (cmatch == strlen(cont_seq))
2453                 {
2454                     cmatch = 0; // complete match.  just reset the counter
2455
2456                     /*
2457                         it's possible for the ICS to not include the space
2458                         at the end of the last word, making our [correct]
2459                         join operation fuse two separate words.  the server
2460                         does this when the space occurs at the width setting.
2461                     */
2462                     if (!buf_len || buf[buf_len-1] != ' ')
2463                     {
2464                         *bp++ = ' ';
2465                         buf_len++;
2466                     }
2467                 }
2468                 continue;
2469             }
2470             else if (cmatch)
2471             {
2472                 /*
2473                     match failed, so we have to copy what matched before
2474                     falling through and copying this character.  In reality,
2475                     this will only ever be just the newline character, but
2476                     it doesn't hurt to be precise.
2477                 */
2478                 strncpy(bp, cont_seq, cmatch);
2479                 bp += cmatch;
2480                 buf_len += cmatch;
2481                 cmatch = 0;
2482             }
2483         }
2484
2485         // copy this char
2486         *bp++ = data[i];
2487         buf_len++;
2488     }
2489
2490         buf[buf_len] = NULLCHAR;
2491 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2492         next_out = 0;
2493         leftover_start = 0;
2494
2495         i = 0;
2496         while (i < buf_len) {
2497             /* Deal with part of the TELNET option negotiation
2498                protocol.  We refuse to do anything beyond the
2499                defaults, except that we allow the WILL ECHO option,
2500                which ICS uses to turn off password echoing when we are
2501                directly connected to it.  We reject this option
2502                if localLineEditing mode is on (always on in xboard)
2503                and we are talking to port 23, which might be a real
2504                telnet server that will try to keep WILL ECHO on permanently.
2505              */
2506             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2507                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2508                 unsigned char option;
2509                 oldi = i;
2510                 switch ((unsigned char) buf[++i]) {
2511                   case TN_WILL:
2512                     if (appData.debugMode)
2513                       fprintf(debugFP, "\n<WILL ");
2514                     switch (option = (unsigned char) buf[++i]) {
2515                       case TN_ECHO:
2516                         if (appData.debugMode)
2517                           fprintf(debugFP, "ECHO ");
2518                         /* Reply only if this is a change, according
2519                            to the protocol rules. */
2520                         if (remoteEchoOption) break;
2521                         if (appData.localLineEditing &&
2522                             atoi(appData.icsPort) == TN_PORT) {
2523                             TelnetRequest(TN_DONT, TN_ECHO);
2524                         } else {
2525                             EchoOff();
2526                             TelnetRequest(TN_DO, TN_ECHO);
2527                             remoteEchoOption = TRUE;
2528                         }
2529                         break;
2530                       default:
2531                         if (appData.debugMode)
2532                           fprintf(debugFP, "%d ", option);
2533                         /* Whatever this is, we don't want it. */
2534                         TelnetRequest(TN_DONT, option);
2535                         break;
2536                     }
2537                     break;
2538                   case TN_WONT:
2539                     if (appData.debugMode)
2540                       fprintf(debugFP, "\n<WONT ");
2541                     switch (option = (unsigned char) buf[++i]) {
2542                       case TN_ECHO:
2543                         if (appData.debugMode)
2544                           fprintf(debugFP, "ECHO ");
2545                         /* Reply only if this is a change, according
2546                            to the protocol rules. */
2547                         if (!remoteEchoOption) break;
2548                         EchoOn();
2549                         TelnetRequest(TN_DONT, TN_ECHO);
2550                         remoteEchoOption = FALSE;
2551                         break;
2552                       default:
2553                         if (appData.debugMode)
2554                           fprintf(debugFP, "%d ", (unsigned char) option);
2555                         /* Whatever this is, it must already be turned
2556                            off, because we never agree to turn on
2557                            anything non-default, so according to the
2558                            protocol rules, we don't reply. */
2559                         break;
2560                     }
2561                     break;
2562                   case TN_DO:
2563                     if (appData.debugMode)
2564                       fprintf(debugFP, "\n<DO ");
2565                     switch (option = (unsigned char) buf[++i]) {
2566                       default:
2567                         /* Whatever this is, we refuse to do it. */
2568                         if (appData.debugMode)
2569                           fprintf(debugFP, "%d ", option);
2570                         TelnetRequest(TN_WONT, option);
2571                         break;
2572                     }
2573                     break;
2574                   case TN_DONT:
2575                     if (appData.debugMode)
2576                       fprintf(debugFP, "\n<DONT ");
2577                     switch (option = (unsigned char) buf[++i]) {
2578                       default:
2579                         if (appData.debugMode)
2580                           fprintf(debugFP, "%d ", option);
2581                         /* Whatever this is, we are already not doing
2582                            it, because we never agree to do anything
2583                            non-default, so according to the protocol
2584                            rules, we don't reply. */
2585                         break;
2586                     }
2587                     break;
2588                   case TN_IAC:
2589                     if (appData.debugMode)
2590                       fprintf(debugFP, "\n<IAC ");
2591                     /* Doubled IAC; pass it through */
2592                     i--;
2593                     break;
2594                   default:
2595                     if (appData.debugMode)
2596                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2597                     /* Drop all other telnet commands on the floor */
2598                     break;
2599                 }
2600                 if (oldi > next_out)
2601                   SendToPlayer(&buf[next_out], oldi - next_out);
2602                 if (++i > next_out)
2603                   next_out = i;
2604                 continue;
2605             }
2606
2607             /* OK, this at least will *usually* work */
2608             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2609                 loggedOn = TRUE;
2610             }
2611
2612             if (loggedOn && !intfSet) {
2613                 if (ics_type == ICS_ICC) {
2614                   snprintf(str, MSG_SIZ,
2615                           "/set-quietly interface %s\n/set-quietly style 12\n",
2616                           programVersion);
2617                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2618                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2619                 } else if (ics_type == ICS_CHESSNET) {
2620                   snprintf(str, MSG_SIZ, "/style 12\n");
2621                 } else {
2622                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2623                   strcat(str, programVersion);
2624                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2625                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2626                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2627 #ifdef WIN32
2628                   strcat(str, "$iset nohighlight 1\n");
2629 #endif
2630                   strcat(str, "$iset lock 1\n$style 12\n");
2631                 }
2632                 SendToICS(str);
2633                 NotifyFrontendLogin();
2634                 intfSet = TRUE;
2635             }
2636
2637             if (started == STARTED_COMMENT) {
2638                 /* Accumulate characters in comment */
2639                 parse[parse_pos++] = buf[i];
2640                 if (buf[i] == '\n') {
2641                     parse[parse_pos] = NULLCHAR;
2642                     if(chattingPartner>=0) {
2643                         char mess[MSG_SIZ];
2644                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2645                         OutputChatMessage(chattingPartner, mess);
2646                         chattingPartner = -1;
2647                         next_out = i+1; // [HGM] suppress printing in ICS window
2648                     } else
2649                     if(!suppressKibitz) // [HGM] kibitz
2650                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2651                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2652                         int nrDigit = 0, nrAlph = 0, j;
2653                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2654                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2655                         parse[parse_pos] = NULLCHAR;
2656                         // try to be smart: if it does not look like search info, it should go to
2657                         // ICS interaction window after all, not to engine-output window.
2658                         for(j=0; j<parse_pos; j++) { // count letters and digits
2659                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2660                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2661                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2662                         }
2663                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2664                             int depth=0; float score;
2665                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2666                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2667                                 pvInfoList[forwardMostMove-1].depth = depth;
2668                                 pvInfoList[forwardMostMove-1].score = 100*score;
2669                             }
2670                             OutputKibitz(suppressKibitz, parse);
2671                         } else {
2672                             char tmp[MSG_SIZ];
2673                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2674                             SendToPlayer(tmp, strlen(tmp));
2675                         }
2676                         next_out = i+1; // [HGM] suppress printing in ICS window
2677                     }
2678                     started = STARTED_NONE;
2679                 } else {
2680                     /* Don't match patterns against characters in comment */
2681                     i++;
2682                     continue;
2683                 }
2684             }
2685             if (started == STARTED_CHATTER) {
2686                 if (buf[i] != '\n') {
2687                     /* Don't match patterns against characters in chatter */
2688                     i++;
2689                     continue;
2690                 }
2691                 started = STARTED_NONE;
2692                 if(suppressKibitz) next_out = i+1;
2693             }
2694
2695             /* Kludge to deal with rcmd protocol */
2696             if (firstTime && looking_at(buf, &i, "\001*")) {
2697                 DisplayFatalError(&buf[1], 0, 1);
2698                 continue;
2699             } else {
2700                 firstTime = FALSE;
2701             }
2702
2703             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2704                 ics_type = ICS_ICC;
2705                 ics_prefix = "/";
2706                 if (appData.debugMode)
2707                   fprintf(debugFP, "ics_type %d\n", ics_type);
2708                 continue;
2709             }
2710             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2711                 ics_type = ICS_FICS;
2712                 ics_prefix = "$";
2713                 if (appData.debugMode)
2714                   fprintf(debugFP, "ics_type %d\n", ics_type);
2715                 continue;
2716             }
2717             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2718                 ics_type = ICS_CHESSNET;
2719                 ics_prefix = "/";
2720                 if (appData.debugMode)
2721                   fprintf(debugFP, "ics_type %d\n", ics_type);
2722                 continue;
2723             }
2724
2725             if (!loggedOn &&
2726                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2727                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2728                  looking_at(buf, &i, "will be \"*\""))) {
2729               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2730               continue;
2731             }
2732
2733             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2734               char buf[MSG_SIZ];
2735               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2736               DisplayIcsInteractionTitle(buf);
2737               have_set_title = TRUE;
2738             }
2739
2740             /* skip finger notes */
2741             if (started == STARTED_NONE &&
2742                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2743                  (buf[i] == '1' && buf[i+1] == '0')) &&
2744                 buf[i+2] == ':' && buf[i+3] == ' ') {
2745               started = STARTED_CHATTER;
2746               i += 3;
2747               continue;
2748             }
2749
2750             oldi = i;
2751             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2752             if(appData.seekGraph) {
2753                 if(soughtPending && MatchSoughtLine(buf+i)) {
2754                     i = strstr(buf+i, "rated") - buf;
2755                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2756                     next_out = leftover_start = i;
2757                     started = STARTED_CHATTER;
2758                     suppressKibitz = TRUE;
2759                     continue;
2760                 }
2761                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2762                         && looking_at(buf, &i, "* ads displayed")) {
2763                     soughtPending = FALSE;
2764                     seekGraphUp = TRUE;
2765                     DrawSeekGraph();
2766                     continue;
2767                 }
2768                 if(appData.autoRefresh) {
2769                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2770                         int s = (ics_type == ICS_ICC); // ICC format differs
2771                         if(seekGraphUp)
2772                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2773                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2774                         looking_at(buf, &i, "*% "); // eat prompt
2775                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2776                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2777                         next_out = i; // suppress
2778                         continue;
2779                     }
2780                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2781                         char *p = star_match[0];
2782                         while(*p) {
2783                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2784                             while(*p && *p++ != ' '); // next
2785                         }
2786                         looking_at(buf, &i, "*% "); // eat prompt
2787                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2788                         next_out = i;
2789                         continue;
2790                     }
2791                 }
2792             }
2793
2794             /* skip formula vars */
2795             if (started == STARTED_NONE &&
2796                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2797               started = STARTED_CHATTER;
2798               i += 3;
2799               continue;
2800             }
2801
2802             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2803             if (appData.autoKibitz && started == STARTED_NONE &&
2804                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2805                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2806                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2807                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2808                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2809                         suppressKibitz = TRUE;
2810                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2811                         next_out = i;
2812                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2813                                 && (gameMode == IcsPlayingWhite)) ||
2814                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2815                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2816                             started = STARTED_CHATTER; // own kibitz we simply discard
2817                         else {
2818                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2819                             parse_pos = 0; parse[0] = NULLCHAR;
2820                             savingComment = TRUE;
2821                             suppressKibitz = gameMode != IcsObserving ? 2 :
2822                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2823                         }
2824                         continue;
2825                 } else
2826                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2827                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2828                          && atoi(star_match[0])) {
2829                     // suppress the acknowledgements of our own autoKibitz
2830                     char *p;
2831                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2832                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2833                     SendToPlayer(star_match[0], strlen(star_match[0]));
2834                     if(looking_at(buf, &i, "*% ")) // eat prompt
2835                         suppressKibitz = FALSE;
2836                     next_out = i;
2837                     continue;
2838                 }
2839             } // [HGM] kibitz: end of patch
2840
2841             // [HGM] chat: intercept tells by users for which we have an open chat window
2842             channel = -1;
2843             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2844                                            looking_at(buf, &i, "* whispers:") ||
2845                                            looking_at(buf, &i, "* kibitzes:") ||
2846                                            looking_at(buf, &i, "* shouts:") ||
2847                                            looking_at(buf, &i, "* c-shouts:") ||
2848                                            looking_at(buf, &i, "--> * ") ||
2849                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2850                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2851                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2852                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2853                 int p;
2854                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2855                 chattingPartner = -1;
2856
2857                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2858                 for(p=0; p<MAX_CHAT; p++) {
2859                     if(channel == atoi(chatPartner[p])) {
2860                     talker[0] = '['; strcat(talker, "] ");
2861                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2862                     chattingPartner = p; break;
2863                     }
2864                 } else
2865                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2866                 for(p=0; p<MAX_CHAT; p++) {
2867                     if(!strcmp("kibitzes", chatPartner[p])) {
2868                         talker[0] = '['; strcat(talker, "] ");
2869                         chattingPartner = p; break;
2870                     }
2871                 } else
2872                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2873                 for(p=0; p<MAX_CHAT; p++) {
2874                     if(!strcmp("whispers", chatPartner[p])) {
2875                         talker[0] = '['; strcat(talker, "] ");
2876                         chattingPartner = p; break;
2877                     }
2878                 } else
2879                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2880                   if(buf[i-8] == '-' && buf[i-3] == 't')
2881                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2882                     if(!strcmp("c-shouts", chatPartner[p])) {
2883                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2884                         chattingPartner = p; break;
2885                     }
2886                   }
2887                   if(chattingPartner < 0)
2888                   for(p=0; p<MAX_CHAT; p++) {
2889                     if(!strcmp("shouts", chatPartner[p])) {
2890                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2891                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2892                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2893                         chattingPartner = p; break;
2894                     }
2895                   }
2896                 }
2897                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2898                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2899                     talker[0] = 0; Colorize(ColorTell, FALSE);
2900                     chattingPartner = p; break;
2901                 }
2902                 if(chattingPartner<0) i = oldi; else {
2903                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2904                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2905                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2906                     started = STARTED_COMMENT;
2907                     parse_pos = 0; parse[0] = NULLCHAR;
2908                     savingComment = 3 + chattingPartner; // counts as TRUE
2909                     suppressKibitz = TRUE;
2910                     continue;
2911                 }
2912             } // [HGM] chat: end of patch
2913
2914             if (appData.zippyTalk || appData.zippyPlay) {
2915                 /* [DM] Backup address for color zippy lines */
2916                 backup = i;
2917 #if ZIPPY
2918                if (loggedOn == TRUE)
2919                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2920                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2921 #endif
2922             } // [DM] 'else { ' deleted
2923                 if (
2924                     /* Regular tells and says */
2925                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2926                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2927                     looking_at(buf, &i, "* says: ") ||
2928                     /* Don't color "message" or "messages" output */
2929                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2930                     looking_at(buf, &i, "*. * at *:*: ") ||
2931                     looking_at(buf, &i, "--* (*:*): ") ||
2932                     /* Message notifications (same color as tells) */
2933                     looking_at(buf, &i, "* has left a message ") ||
2934                     looking_at(buf, &i, "* just sent you a message:\n") ||
2935                     /* Whispers and kibitzes */
2936                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2937                     looking_at(buf, &i, "* kibitzes: ") ||
2938                     /* Channel tells */
2939                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2940
2941                   if (tkind == 1 && strchr(star_match[0], ':')) {
2942                       /* Avoid "tells you:" spoofs in channels */
2943                      tkind = 3;
2944                   }
2945                   if (star_match[0][0] == NULLCHAR ||
2946                       strchr(star_match[0], ' ') ||
2947                       (tkind == 3 && strchr(star_match[1], ' '))) {
2948                     /* Reject bogus matches */
2949                     i = oldi;
2950                   } else {
2951                     if (appData.colorize) {
2952                       if (oldi > next_out) {
2953                         SendToPlayer(&buf[next_out], oldi - next_out);
2954                         next_out = oldi;
2955                       }
2956                       switch (tkind) {
2957                       case 1:
2958                         Colorize(ColorTell, FALSE);
2959                         curColor = ColorTell;
2960                         break;
2961                       case 2:
2962                         Colorize(ColorKibitz, FALSE);
2963                         curColor = ColorKibitz;
2964                         break;
2965                       case 3:
2966                         p = strrchr(star_match[1], '(');
2967                         if (p == NULL) {
2968                           p = star_match[1];
2969                         } else {
2970                           p++;
2971                         }
2972                         if (atoi(p) == 1) {
2973                           Colorize(ColorChannel1, FALSE);
2974                           curColor = ColorChannel1;
2975                         } else {
2976                           Colorize(ColorChannel, FALSE);
2977                           curColor = ColorChannel;
2978                         }
2979                         break;
2980                       case 5:
2981                         curColor = ColorNormal;
2982                         break;
2983                       }
2984                     }
2985                     if (started == STARTED_NONE && appData.autoComment &&
2986                         (gameMode == IcsObserving ||
2987                          gameMode == IcsPlayingWhite ||
2988                          gameMode == IcsPlayingBlack)) {
2989                       parse_pos = i - oldi;
2990                       memcpy(parse, &buf[oldi], parse_pos);
2991                       parse[parse_pos] = NULLCHAR;
2992                       started = STARTED_COMMENT;
2993                       savingComment = TRUE;
2994                     } else {
2995                       started = STARTED_CHATTER;
2996                       savingComment = FALSE;
2997                     }
2998                     loggedOn = TRUE;
2999                     continue;
3000                   }
3001                 }
3002
3003                 if (looking_at(buf, &i, "* s-shouts: ") ||
3004                     looking_at(buf, &i, "* c-shouts: ")) {
3005                     if (appData.colorize) {
3006                         if (oldi > next_out) {
3007                             SendToPlayer(&buf[next_out], oldi - next_out);
3008                             next_out = oldi;
3009                         }
3010                         Colorize(ColorSShout, FALSE);
3011                         curColor = ColorSShout;
3012                     }
3013                     loggedOn = TRUE;
3014                     started = STARTED_CHATTER;
3015                     continue;
3016                 }
3017
3018                 if (looking_at(buf, &i, "--->")) {
3019                     loggedOn = TRUE;
3020                     continue;
3021                 }
3022
3023                 if (looking_at(buf, &i, "* shouts: ") ||
3024                     looking_at(buf, &i, "--> ")) {
3025                     if (appData.colorize) {
3026                         if (oldi > next_out) {
3027                             SendToPlayer(&buf[next_out], oldi - next_out);
3028                             next_out = oldi;
3029                         }
3030                         Colorize(ColorShout, FALSE);
3031                         curColor = ColorShout;
3032                     }
3033                     loggedOn = TRUE;
3034                     started = STARTED_CHATTER;
3035                     continue;
3036                 }
3037
3038                 if (looking_at( buf, &i, "Challenge:")) {
3039                     if (appData.colorize) {
3040                         if (oldi > next_out) {
3041                             SendToPlayer(&buf[next_out], oldi - next_out);
3042                             next_out = oldi;
3043                         }
3044                         Colorize(ColorChallenge, FALSE);
3045                         curColor = ColorChallenge;
3046                     }
3047                     loggedOn = TRUE;
3048                     continue;
3049                 }
3050
3051                 if (looking_at(buf, &i, "* offers you") ||
3052                     looking_at(buf, &i, "* offers to be") ||
3053                     looking_at(buf, &i, "* would like to") ||
3054                     looking_at(buf, &i, "* requests to") ||
3055                     looking_at(buf, &i, "Your opponent offers") ||
3056                     looking_at(buf, &i, "Your opponent requests")) {
3057
3058                     if (appData.colorize) {
3059                         if (oldi > next_out) {
3060                             SendToPlayer(&buf[next_out], oldi - next_out);
3061                             next_out = oldi;
3062                         }
3063                         Colorize(ColorRequest, FALSE);
3064                         curColor = ColorRequest;
3065                     }
3066                     continue;
3067                 }
3068
3069                 if (looking_at(buf, &i, "* (*) seeking")) {
3070                     if (appData.colorize) {
3071                         if (oldi > next_out) {
3072                             SendToPlayer(&buf[next_out], oldi - next_out);
3073                             next_out = oldi;
3074                         }
3075                         Colorize(ColorSeek, FALSE);
3076                         curColor = ColorSeek;
3077                     }
3078                     continue;
3079             }
3080
3081             if (looking_at(buf, &i, "\\   ")) {
3082                 if (prevColor != ColorNormal) {
3083                     if (oldi > next_out) {
3084                         SendToPlayer(&buf[next_out], oldi - next_out);
3085                         next_out = oldi;
3086                     }
3087                     Colorize(prevColor, TRUE);
3088                     curColor = prevColor;
3089                 }
3090                 if (savingComment) {
3091                     parse_pos = i - oldi;
3092                     memcpy(parse, &buf[oldi], parse_pos);
3093                     parse[parse_pos] = NULLCHAR;
3094                     started = STARTED_COMMENT;
3095                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3096                         chattingPartner = savingComment - 3; // kludge to remember the box
3097                 } else {
3098                     started = STARTED_CHATTER;
3099                 }
3100                 continue;
3101             }
3102
3103             if (looking_at(buf, &i, "Black Strength :") ||
3104                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3105                 looking_at(buf, &i, "<10>") ||
3106                 looking_at(buf, &i, "#@#")) {
3107                 /* Wrong board style */
3108                 loggedOn = TRUE;
3109                 SendToICS(ics_prefix);
3110                 SendToICS("set style 12\n");
3111                 SendToICS(ics_prefix);
3112                 SendToICS("refresh\n");
3113                 continue;
3114             }
3115
3116             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3117                 ICSInitScript();
3118                 have_sent_ICS_logon = 1;
3119                 /* if we don't send the login/password via icsLogon, use special readline
3120                    code for it */
3121                 if (strlen(appData.icsLogon)==0)
3122                   {
3123                     sending_ICS_password = 0; // in case we come back to login
3124                     sending_ICS_login = 1;
3125                   };
3126                 continue;
3127             }
3128             /* need to shadow the password */
3129             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3130               /* if we don't send the login/password via icsLogon, use special readline
3131                  code for it */
3132               if (strlen(appData.icsLogon)==0)
3133                 sending_ICS_password = 1;
3134               continue;
3135             }
3136
3137             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3138                 (looking_at(buf, &i, "\n<12> ") ||
3139                  looking_at(buf, &i, "<12> "))) {
3140                 loggedOn = TRUE;
3141                 if (oldi > next_out) {
3142                     SendToPlayer(&buf[next_out], oldi - next_out);
3143                 }
3144                 next_out = i;
3145                 started = STARTED_BOARD;
3146                 parse_pos = 0;
3147                 continue;
3148             }
3149
3150             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3151                 looking_at(buf, &i, "<b1> ")) {
3152                 if (oldi > next_out) {
3153                     SendToPlayer(&buf[next_out], oldi - next_out);
3154                 }
3155                 next_out = i;
3156                 started = STARTED_HOLDINGS;
3157                 parse_pos = 0;
3158                 continue;
3159             }
3160
3161             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3162                 loggedOn = TRUE;
3163                 /* Header for a move list -- first line */
3164
3165                 switch (ics_getting_history) {
3166                   case H_FALSE:
3167                     switch (gameMode) {
3168                       case IcsIdle:
3169                       case BeginningOfGame:
3170                         /* User typed "moves" or "oldmoves" while we
3171                            were idle.  Pretend we asked for these
3172                            moves and soak them up so user can step
3173                            through them and/or save them.
3174                            */
3175                         Reset(FALSE, TRUE);
3176                         gameMode = IcsObserving;
3177                         ModeHighlight();
3178                         ics_gamenum = -1;
3179                         ics_getting_history = H_GOT_UNREQ_HEADER;
3180                         break;
3181                       case EditGame: /*?*/
3182                       case EditPosition: /*?*/
3183                         /* Should above feature work in these modes too? */
3184                         /* For now it doesn't */
3185                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3186                         break;
3187                       default:
3188                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3189                         break;
3190                     }
3191                     break;
3192                   case H_REQUESTED:
3193                     /* Is this the right one? */
3194                     if (gameInfo.white && gameInfo.black &&
3195                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3196                         strcmp(gameInfo.black, star_match[2]) == 0) {
3197                         /* All is well */
3198                         ics_getting_history = H_GOT_REQ_HEADER;
3199                     }
3200                     break;
3201                   case H_GOT_REQ_HEADER:
3202                   case H_GOT_UNREQ_HEADER:
3203                   case H_GOT_UNWANTED_HEADER:
3204                   case H_GETTING_MOVES:
3205                     /* Should not happen */
3206                     DisplayError(_("Error gathering move list: two headers"), 0);
3207                     ics_getting_history = H_FALSE;
3208                     break;
3209                 }
3210
3211                 /* Save player ratings into gameInfo if needed */
3212                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3213                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3214                     (gameInfo.whiteRating == -1 ||
3215                      gameInfo.blackRating == -1)) {
3216
3217                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3218                     gameInfo.blackRating = string_to_rating(star_match[3]);
3219                     if (appData.debugMode)
3220                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3221                               gameInfo.whiteRating, gameInfo.blackRating);
3222                 }
3223                 continue;
3224             }
3225
3226             if (looking_at(buf, &i,
3227               "* * match, initial time: * minute*, increment: * second")) {
3228                 /* Header for a move list -- second line */
3229                 /* Initial board will follow if this is a wild game */
3230                 if (gameInfo.event != NULL) free(gameInfo.event);
3231                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3232                 gameInfo.event = StrSave(str);
3233                 /* [HGM] we switched variant. Translate boards if needed. */
3234                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3235                 continue;
3236             }
3237
3238             if (looking_at(buf, &i, "Move  ")) {
3239                 /* Beginning of a move list */
3240                 switch (ics_getting_history) {
3241                   case H_FALSE:
3242                     /* Normally should not happen */
3243                     /* Maybe user hit reset while we were parsing */
3244                     break;
3245                   case H_REQUESTED:
3246                     /* Happens if we are ignoring a move list that is not
3247                      * the one we just requested.  Common if the user
3248                      * tries to observe two games without turning off
3249                      * getMoveList */
3250                     break;
3251                   case H_GETTING_MOVES:
3252                     /* Should not happen */
3253                     DisplayError(_("Error gathering move list: nested"), 0);
3254                     ics_getting_history = H_FALSE;
3255                     break;
3256                   case H_GOT_REQ_HEADER:
3257                     ics_getting_history = H_GETTING_MOVES;
3258                     started = STARTED_MOVES;
3259                     parse_pos = 0;
3260                     if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                     }
3263                     break;
3264                   case H_GOT_UNREQ_HEADER:
3265                     ics_getting_history = H_GETTING_MOVES;
3266                     started = STARTED_MOVES_NOHIDE;
3267                     parse_pos = 0;
3268                     break;
3269                   case H_GOT_UNWANTED_HEADER:
3270                     ics_getting_history = H_FALSE;
3271                     break;
3272                 }
3273                 continue;
3274             }
3275
3276             if (looking_at(buf, &i, "% ") ||
3277                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3278                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3279                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3280                     soughtPending = FALSE;
3281                     seekGraphUp = TRUE;
3282                     DrawSeekGraph();
3283                 }
3284                 if(suppressKibitz) next_out = i;
3285                 savingComment = FALSE;
3286                 suppressKibitz = 0;
3287                 switch (started) {
3288                   case STARTED_MOVES:
3289                   case STARTED_MOVES_NOHIDE:
3290                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3291                     parse[parse_pos + i - oldi] = NULLCHAR;
3292                     ParseGameHistory(parse);
3293 #if ZIPPY
3294                     if (appData.zippyPlay && first.initDone) {
3295                         FeedMovesToProgram(&first, forwardMostMove);
3296                         if (gameMode == IcsPlayingWhite) {
3297                             if (WhiteOnMove(forwardMostMove)) {
3298                                 if (first.sendTime) {
3299                                   if (first.useColors) {
3300                                     SendToProgram("black\n", &first);
3301                                   }
3302                                   SendTimeRemaining(&first, TRUE);
3303                                 }
3304                                 if (first.useColors) {
3305                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3306                                 }
3307                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3308                                 first.maybeThinking = TRUE;
3309                             } else {
3310                                 if (first.usePlayother) {
3311                                   if (first.sendTime) {
3312                                     SendTimeRemaining(&first, TRUE);
3313                                   }
3314                                   SendToProgram("playother\n", &first);
3315                                   firstMove = FALSE;
3316                                 } else {
3317                                   firstMove = TRUE;
3318                                 }
3319                             }
3320                         } else if (gameMode == IcsPlayingBlack) {
3321                             if (!WhiteOnMove(forwardMostMove)) {
3322                                 if (first.sendTime) {
3323                                   if (first.useColors) {
3324                                     SendToProgram("white\n", &first);
3325                                   }
3326                                   SendTimeRemaining(&first, FALSE);
3327                                 }
3328                                 if (first.useColors) {
3329                                   SendToProgram("black\n", &first);
3330                                 }
3331                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3332                                 first.maybeThinking = TRUE;
3333                             } else {
3334                                 if (first.usePlayother) {
3335                                   if (first.sendTime) {
3336                                     SendTimeRemaining(&first, FALSE);
3337                                   }
3338                                   SendToProgram("playother\n", &first);
3339                                   firstMove = FALSE;
3340                                 } else {
3341                                   firstMove = TRUE;
3342                                 }
3343                             }
3344                         }
3345                     }
3346 #endif
3347                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3348                         /* Moves came from oldmoves or moves command
3349                            while we weren't doing anything else.
3350                            */
3351                         currentMove = forwardMostMove;
3352                         ClearHighlights();/*!!could figure this out*/
3353                         flipView = appData.flipView;
3354                         DrawPosition(TRUE, boards[currentMove]);
3355                         DisplayBothClocks();
3356                         snprintf(str, MSG_SIZ, "%s vs. %s",
3357                                 gameInfo.white, gameInfo.black);
3358                         DisplayTitle(str);
3359                         gameMode = IcsIdle;
3360                     } else {
3361                         /* Moves were history of an active game */
3362                         if (gameInfo.resultDetails != NULL) {
3363                             free(gameInfo.resultDetails);
3364                             gameInfo.resultDetails = NULL;
3365                         }
3366                     }
3367                     HistorySet(parseList, backwardMostMove,
3368                                forwardMostMove, currentMove-1);
3369                     DisplayMove(currentMove - 1);
3370                     if (started == STARTED_MOVES) next_out = i;
3371                     started = STARTED_NONE;
3372                     ics_getting_history = H_FALSE;
3373                     break;
3374
3375                   case STARTED_OBSERVE:
3376                     started = STARTED_NONE;
3377                     SendToICS(ics_prefix);
3378                     SendToICS("refresh\n");
3379                     break;
3380
3381                   default:
3382                     break;
3383                 }
3384                 if(bookHit) { // [HGM] book: simulate book reply
3385                     static char bookMove[MSG_SIZ]; // a bit generous?
3386
3387                     programStats.nodes = programStats.depth = programStats.time =
3388                     programStats.score = programStats.got_only_move = 0;
3389                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3390
3391                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3392                     strcat(bookMove, bookHit);
3393                     HandleMachineMove(bookMove, &first);
3394                 }
3395                 continue;
3396             }
3397
3398             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3399                  started == STARTED_HOLDINGS ||
3400                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3401                 /* Accumulate characters in move list or board */
3402                 parse[parse_pos++] = buf[i];
3403             }
3404
3405             /* Start of game messages.  Mostly we detect start of game
3406                when the first board image arrives.  On some versions
3407                of the ICS, though, we need to do a "refresh" after starting
3408                to observe in order to get the current board right away. */
3409             if (looking_at(buf, &i, "Adding game * to observation list")) {
3410                 started = STARTED_OBSERVE;
3411                 continue;
3412             }
3413
3414             /* Handle auto-observe */
3415             if (appData.autoObserve &&
3416                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3417                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3418                 char *player;
3419                 /* Choose the player that was highlighted, if any. */
3420                 if (star_match[0][0] == '\033' ||
3421                     star_match[1][0] != '\033') {
3422                     player = star_match[0];
3423                 } else {
3424                     player = star_match[2];
3425                 }
3426                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3427                         ics_prefix, StripHighlightAndTitle(player));
3428                 SendToICS(str);
3429
3430                 /* Save ratings from notify string */
3431                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3432                 player1Rating = string_to_rating(star_match[1]);
3433                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3434                 player2Rating = string_to_rating(star_match[3]);
3435
3436                 if (appData.debugMode)
3437                   fprintf(debugFP,
3438                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3439                           player1Name, player1Rating,
3440                           player2Name, player2Rating);
3441
3442                 continue;
3443             }
3444
3445             /* Deal with automatic examine mode after a game,
3446                and with IcsObserving -> IcsExamining transition */
3447             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3448                 looking_at(buf, &i, "has made you an examiner of game *")) {
3449
3450                 int gamenum = atoi(star_match[0]);
3451                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3452                     gamenum == ics_gamenum) {
3453                     /* We were already playing or observing this game;
3454                        no need to refetch history */
3455                     gameMode = IcsExamining;
3456                     if (pausing) {
3457                         pauseExamForwardMostMove = forwardMostMove;
3458                     } else if (currentMove < forwardMostMove) {
3459                         ForwardInner(forwardMostMove);
3460                     }
3461                 } else {
3462                     /* I don't think this case really can happen */
3463                     SendToICS(ics_prefix);
3464                     SendToICS("refresh\n");
3465                 }
3466                 continue;
3467             }
3468
3469             /* Error messages */
3470 //          if (ics_user_moved) {
3471             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3472                 if (looking_at(buf, &i, "Illegal move") ||
3473                     looking_at(buf, &i, "Not a legal move") ||
3474                     looking_at(buf, &i, "Your king is in check") ||
3475                     looking_at(buf, &i, "It isn't your turn") ||
3476                     looking_at(buf, &i, "It is not your move")) {
3477                     /* Illegal move */
3478                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3479                         currentMove = forwardMostMove-1;
3480                         DisplayMove(currentMove - 1); /* before DMError */
3481                         DrawPosition(FALSE, boards[currentMove]);
3482                         SwitchClocks(forwardMostMove-1); // [HGM] race
3483                         DisplayBothClocks();
3484                     }
3485                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3486                     ics_user_moved = 0;
3487                     continue;
3488                 }
3489             }
3490
3491             if (looking_at(buf, &i, "still have time") ||
3492                 looking_at(buf, &i, "not out of time") ||
3493                 looking_at(buf, &i, "either player is out of time") ||
3494                 looking_at(buf, &i, "has timeseal; checking")) {
3495                 /* We must have called his flag a little too soon */
3496                 whiteFlag = blackFlag = FALSE;
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i, "added * seconds to") ||
3501                 looking_at(buf, &i, "seconds were added to")) {
3502                 /* Update the clocks */
3503                 SendToICS(ics_prefix);
3504                 SendToICS("refresh\n");
3505                 continue;
3506             }
3507
3508             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3509                 ics_clock_paused = TRUE;
3510                 StopClocks();
3511                 continue;
3512             }
3513
3514             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3515                 ics_clock_paused = FALSE;
3516                 StartClocks();
3517                 continue;
3518             }
3519
3520             /* Grab player ratings from the Creating: message.
3521                Note we have to check for the special case when
3522                the ICS inserts things like [white] or [black]. */
3523             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3524                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3525                 /* star_matches:
3526                    0    player 1 name (not necessarily white)
3527                    1    player 1 rating
3528                    2    empty, white, or black (IGNORED)
3529                    3    player 2 name (not necessarily black)
3530                    4    player 2 rating
3531
3532                    The names/ratings are sorted out when the game
3533                    actually starts (below).
3534                 */
3535                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3536                 player1Rating = string_to_rating(star_match[1]);
3537                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3538                 player2Rating = string_to_rating(star_match[4]);
3539
3540                 if (appData.debugMode)
3541                   fprintf(debugFP,
3542                           "Ratings from 'Creating:' %s %d, %s %d\n",
3543                           player1Name, player1Rating,
3544                           player2Name, player2Rating);
3545
3546                 continue;
3547             }
3548
3549             /* Improved generic start/end-of-game messages */
3550             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3551                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3552                 /* If tkind == 0: */
3553                 /* star_match[0] is the game number */
3554                 /*           [1] is the white player's name */
3555                 /*           [2] is the black player's name */
3556                 /* For end-of-game: */
3557                 /*           [3] is the reason for the game end */
3558                 /*           [4] is a PGN end game-token, preceded by " " */
3559                 /* For start-of-game: */
3560                 /*           [3] begins with "Creating" or "Continuing" */
3561                 /*           [4] is " *" or empty (don't care). */
3562                 int gamenum = atoi(star_match[0]);
3563                 char *whitename, *blackname, *why, *endtoken;
3564                 ChessMove endtype = EndOfFile;
3565
3566                 if (tkind == 0) {
3567                   whitename = star_match[1];
3568                   blackname = star_match[2];
3569                   why = star_match[3];
3570                   endtoken = star_match[4];
3571                 } else {
3572                   whitename = star_match[1];
3573                   blackname = star_match[3];
3574                   why = star_match[5];
3575                   endtoken = star_match[6];
3576                 }
3577
3578                 /* Game start messages */
3579                 if (strncmp(why, "Creating ", 9) == 0 ||
3580                     strncmp(why, "Continuing ", 11) == 0) {
3581                     gs_gamenum = gamenum;
3582                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3583                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3584 #if ZIPPY
3585                     if (appData.zippyPlay) {
3586                         ZippyGameStart(whitename, blackname);
3587                     }
3588 #endif /*ZIPPY*/
3589                     partnerBoardValid = FALSE; // [HGM] bughouse
3590                     continue;
3591                 }
3592
3593                 /* Game end messages */
3594                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3595                     ics_gamenum != gamenum) {
3596                     continue;
3597                 }
3598                 while (endtoken[0] == ' ') endtoken++;
3599                 switch (endtoken[0]) {
3600                   case '*':
3601                   default:
3602                     endtype = GameUnfinished;
3603                     break;
3604                   case '0':
3605                     endtype = BlackWins;
3606                     break;
3607                   case '1':
3608                     if (endtoken[1] == '/')
3609                       endtype = GameIsDrawn;
3610                     else
3611                       endtype = WhiteWins;
3612                     break;
3613                 }
3614                 GameEnds(endtype, why, GE_ICS);
3615 #if ZIPPY
3616                 if (appData.zippyPlay && first.initDone) {
3617                     ZippyGameEnd(endtype, why);
3618                     if (first.pr == NULL) {
3619                       /* Start the next process early so that we'll
3620                          be ready for the next challenge */
3621                       StartChessProgram(&first);
3622                     }
3623                     /* Send "new" early, in case this command takes
3624                        a long time to finish, so that we'll be ready
3625                        for the next challenge. */
3626                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3627                     Reset(TRUE, TRUE);
3628                 }
3629 #endif /*ZIPPY*/
3630                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3631                 continue;
3632             }
3633
3634             if (looking_at(buf, &i, "Removing game * from observation") ||
3635                 looking_at(buf, &i, "no longer observing game *") ||
3636                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3637                 if (gameMode == IcsObserving &&
3638                     atoi(star_match[0]) == ics_gamenum)
3639                   {
3640                       /* icsEngineAnalyze */
3641                       if (appData.icsEngineAnalyze) {
3642                             ExitAnalyzeMode();
3643                             ModeHighlight();
3644                       }
3645                       StopClocks();
3646                       gameMode = IcsIdle;
3647                       ics_gamenum = -1;
3648                       ics_user_moved = FALSE;
3649                   }
3650                 continue;
3651             }
3652
3653             if (looking_at(buf, &i, "no longer examining game *")) {
3654                 if (gameMode == IcsExamining &&
3655                     atoi(star_match[0]) == ics_gamenum)
3656                   {
3657                       gameMode = IcsIdle;
3658                       ics_gamenum = -1;
3659                       ics_user_moved = FALSE;
3660                   }
3661                 continue;
3662             }
3663
3664             /* Advance leftover_start past any newlines we find,
3665                so only partial lines can get reparsed */
3666             if (looking_at(buf, &i, "\n")) {
3667                 prevColor = curColor;
3668                 if (curColor != ColorNormal) {
3669                     if (oldi > next_out) {
3670                         SendToPlayer(&buf[next_out], oldi - next_out);
3671                         next_out = oldi;
3672                     }
3673                     Colorize(ColorNormal, FALSE);
3674                     curColor = ColorNormal;
3675                 }
3676                 if (started == STARTED_BOARD) {
3677                     started = STARTED_NONE;
3678                     parse[parse_pos] = NULLCHAR;
3679                     ParseBoard12(parse);
3680                     ics_user_moved = 0;
3681
3682                     /* Send premove here */
3683                     if (appData.premove) {
3684                       char str[MSG_SIZ];
3685                       if (currentMove == 0 &&
3686                           gameMode == IcsPlayingWhite &&
3687                           appData.premoveWhite) {
3688                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3689                         if (appData.debugMode)
3690                           fprintf(debugFP, "Sending premove:\n");
3691                         SendToICS(str);
3692                       } else if (currentMove == 1 &&
3693                                  gameMode == IcsPlayingBlack &&
3694                                  appData.premoveBlack) {
3695                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3696                         if (appData.debugMode)
3697                           fprintf(debugFP, "Sending premove:\n");
3698                         SendToICS(str);
3699                       } else if (gotPremove) {
3700                         gotPremove = 0;
3701                         ClearPremoveHighlights();
3702                         if (appData.debugMode)
3703                           fprintf(debugFP, "Sending premove:\n");
3704                           UserMoveEvent(premoveFromX, premoveFromY,
3705                                         premoveToX, premoveToY,
3706                                         premovePromoChar);
3707                       }
3708                     }
3709
3710                     /* Usually suppress following prompt */
3711                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3712                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3713                         if (looking_at(buf, &i, "*% ")) {
3714                             savingComment = FALSE;
3715                             suppressKibitz = 0;
3716                         }
3717                     }
3718                     next_out = i;
3719                 } else if (started == STARTED_HOLDINGS) {
3720                     int gamenum;
3721                     char new_piece[MSG_SIZ];
3722                     started = STARTED_NONE;
3723                     parse[parse_pos] = NULLCHAR;
3724                     if (appData.debugMode)
3725                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3726                                                         parse, currentMove);
3727                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3728                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3729                         if (gameInfo.variant == VariantNormal) {
3730                           /* [HGM] We seem to switch variant during a game!
3731                            * Presumably no holdings were displayed, so we have
3732                            * to move the position two files to the right to
3733                            * create room for them!
3734                            */
3735                           VariantClass newVariant;
3736                           switch(gameInfo.boardWidth) { // base guess on board width
3737                                 case 9:  newVariant = VariantShogi; break;
3738                                 case 10: newVariant = VariantGreat; break;
3739                                 default: newVariant = VariantCrazyhouse; break;
3740                           }
3741                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3742                           /* Get a move list just to see the header, which
3743                              will tell us whether this is really bug or zh */
3744                           if (ics_getting_history == H_FALSE) {
3745                             ics_getting_history = H_REQUESTED;
3746                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3747                             SendToICS(str);
3748                           }
3749                         }
3750                         new_piece[0] = NULLCHAR;
3751                         sscanf(parse, "game %d white [%s black [%s <- %s",
3752                                &gamenum, white_holding, black_holding,
3753                                new_piece);
3754                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3755                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3756                         /* [HGM] copy holdings to board holdings area */
3757                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3758                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3759                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3760 #if ZIPPY
3761                         if (appData.zippyPlay && first.initDone) {
3762                             ZippyHoldings(white_holding, black_holding,
3763                                           new_piece);
3764                         }
3765 #endif /*ZIPPY*/
3766                         if (tinyLayout || smallLayout) {
3767                             char wh[16], bh[16];
3768                             PackHolding(wh, white_holding);
3769                             PackHolding(bh, black_holding);
3770                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3771                                     gameInfo.white, gameInfo.black);
3772                         } else {
3773                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3774                                     gameInfo.white, white_holding,
3775                                     gameInfo.black, black_holding);
3776                         }
3777                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3778                         DrawPosition(FALSE, boards[currentMove]);
3779                         DisplayTitle(str);
3780                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3781                         sscanf(parse, "game %d white [%s black [%s <- %s",
3782                                &gamenum, white_holding, black_holding,
3783                                new_piece);
3784                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3785                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3786                         /* [HGM] copy holdings to partner-board holdings area */
3787                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3788                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3789                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3790                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3791                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3792                       }
3793                     }
3794                     /* Suppress following prompt */
3795                     if (looking_at(buf, &i, "*% ")) {
3796                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3797                         savingComment = FALSE;
3798                         suppressKibitz = 0;
3799                     }
3800                     next_out = i;
3801                 }
3802                 continue;
3803             }
3804
3805             i++;                /* skip unparsed character and loop back */
3806         }
3807
3808         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3809 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3810 //          SendToPlayer(&buf[next_out], i - next_out);
3811             started != STARTED_HOLDINGS && leftover_start > next_out) {
3812             SendToPlayer(&buf[next_out], leftover_start - next_out);
3813             next_out = i;
3814         }
3815
3816         leftover_len = buf_len - leftover_start;
3817         /* if buffer ends with something we couldn't parse,
3818            reparse it after appending the next read */
3819
3820     } else if (count == 0) {
3821         RemoveInputSource(isr);
3822         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3823     } else {
3824         DisplayFatalError(_("Error reading from ICS"), error, 1);
3825     }
3826 }
3827
3828
3829 /* Board style 12 looks like this:
3830
3831    <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
3832
3833  * The "<12> " is stripped before it gets to this routine.  The two
3834  * trailing 0's (flip state and clock ticking) are later addition, and
3835  * some chess servers may not have them, or may have only the first.
3836  * Additional trailing fields may be added in the future.
3837  */
3838
3839 #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"
3840
3841 #define RELATION_OBSERVING_PLAYED    0
3842 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3843 #define RELATION_PLAYING_MYMOVE      1
3844 #define RELATION_PLAYING_NOTMYMOVE  -1
3845 #define RELATION_EXAMINING           2
3846 #define RELATION_ISOLATED_BOARD     -3
3847 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3848
3849 void
3850 ParseBoard12(string)
3851      char *string;
3852 {
3853     GameMode newGameMode;
3854     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3855     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3856     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3857     char to_play, board_chars[200];
3858     char move_str[500], str[500], elapsed_time[500];
3859     char black[32], white[32];
3860     Board board;
3861     int prevMove = currentMove;
3862     int ticking = 2;
3863     ChessMove moveType;
3864     int fromX, fromY, toX, toY;
3865     char promoChar;
3866     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3867     char *bookHit = NULL; // [HGM] book
3868     Boolean weird = FALSE, reqFlag = FALSE;
3869
3870     fromX = fromY = toX = toY = -1;
3871
3872     newGame = FALSE;
3873
3874     if (appData.debugMode)
3875       fprintf(debugFP, _("Parsing board: %s\n"), string);
3876
3877     move_str[0] = NULLCHAR;
3878     elapsed_time[0] = NULLCHAR;
3879     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3880         int  i = 0, j;
3881         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3882             if(string[i] == ' ') { ranks++; files = 0; }
3883             else files++;
3884             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3885             i++;
3886         }
3887         for(j = 0; j <i; j++) board_chars[j] = string[j];
3888         board_chars[i] = '\0';
3889         string += i + 1;
3890     }
3891     n = sscanf(string, PATTERN, &to_play, &double_push,
3892                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3893                &gamenum, white, black, &relation, &basetime, &increment,
3894                &white_stren, &black_stren, &white_time, &black_time,
3895                &moveNum, str, elapsed_time, move_str, &ics_flip,
3896                &ticking);
3897
3898     if (n < 21) {
3899         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3900         DisplayError(str, 0);
3901         return;
3902     }
3903
3904     /* Convert the move number to internal form */
3905     moveNum = (moveNum - 1) * 2;
3906     if (to_play == 'B') moveNum++;
3907     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3908       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3909                         0, 1);
3910       return;
3911     }
3912
3913     switch (relation) {
3914       case RELATION_OBSERVING_PLAYED:
3915       case RELATION_OBSERVING_STATIC:
3916         if (gamenum == -1) {
3917             /* Old ICC buglet */
3918             relation = RELATION_OBSERVING_STATIC;
3919         }
3920         newGameMode = IcsObserving;
3921         break;
3922       case RELATION_PLAYING_MYMOVE:
3923       case RELATION_PLAYING_NOTMYMOVE:
3924         newGameMode =
3925           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3926             IcsPlayingWhite : IcsPlayingBlack;
3927         break;
3928       case RELATION_EXAMINING:
3929         newGameMode = IcsExamining;
3930         break;
3931       case RELATION_ISOLATED_BOARD:
3932       default:
3933         /* Just display this board.  If user was doing something else,
3934            we will forget about it until the next board comes. */
3935         newGameMode = IcsIdle;
3936         break;
3937       case RELATION_STARTING_POSITION:
3938         newGameMode = gameMode;
3939         break;
3940     }
3941
3942     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3943          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3944       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3945       char *toSqr;
3946       for (k = 0; k < ranks; k++) {
3947         for (j = 0; j < files; j++)
3948           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3949         if(gameInfo.holdingsWidth > 1) {
3950              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3951              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3952         }
3953       }
3954       CopyBoard(partnerBoard, board);
3955       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3956         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3957         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3958       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3959       if(toSqr = strchr(str, '-')) {
3960         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3961         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3962       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3963       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3964       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3965       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3966       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3967       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3968                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3969       DisplayMessage(partnerStatus, "");
3970         partnerBoardValid = TRUE;
3971       return;
3972     }
3973
3974     /* Modify behavior for initial board display on move listing
3975        of wild games.
3976        */
3977     switch (ics_getting_history) {
3978       case H_FALSE:
3979       case H_REQUESTED:
3980         break;
3981       case H_GOT_REQ_HEADER:
3982       case H_GOT_UNREQ_HEADER:
3983         /* This is the initial position of the current game */
3984         gamenum = ics_gamenum;
3985         moveNum = 0;            /* old ICS bug workaround */
3986         if (to_play == 'B') {
3987           startedFromSetupPosition = TRUE;
3988           blackPlaysFirst = TRUE;
3989           moveNum = 1;
3990           if (forwardMostMove == 0) forwardMostMove = 1;
3991           if (backwardMostMove == 0) backwardMostMove = 1;
3992           if (currentMove == 0) currentMove = 1;
3993         }
3994         newGameMode = gameMode;
3995         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3996         break;
3997       case H_GOT_UNWANTED_HEADER:
3998         /* This is an initial board that we don't want */
3999         return;
4000       case H_GETTING_MOVES:
4001         /* Should not happen */
4002         DisplayError(_("Error gathering move list: extra board"), 0);
4003         ics_getting_history = H_FALSE;
4004         return;
4005     }
4006
4007    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4008                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4009      /* [HGM] We seem to have switched variant unexpectedly
4010       * Try to guess new variant from board size
4011       */
4012           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4013           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4014           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4015           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4016           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4017           if(!weird) newVariant = VariantNormal;
4018           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4019           /* Get a move list just to see the header, which
4020              will tell us whether this is really bug or zh */
4021           if (ics_getting_history == H_FALSE) {
4022             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4023             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4024             SendToICS(str);
4025           }
4026     }
4027
4028     /* Take action if this is the first board of a new game, or of a
4029        different game than is currently being displayed.  */
4030     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4031         relation == RELATION_ISOLATED_BOARD) {
4032
4033         /* Forget the old game and get the history (if any) of the new one */
4034         if (gameMode != BeginningOfGame) {
4035           Reset(TRUE, TRUE);
4036         }
4037         newGame = TRUE;
4038         if (appData.autoRaiseBoard) BoardToTop();
4039         prevMove = -3;
4040         if (gamenum == -1) {
4041             newGameMode = IcsIdle;
4042         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4043                    appData.getMoveList && !reqFlag) {
4044             /* Need to get game history */
4045             ics_getting_history = H_REQUESTED;
4046             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4047             SendToICS(str);
4048         }
4049
4050         /* Initially flip the board to have black on the bottom if playing
4051            black or if the ICS flip flag is set, but let the user change
4052            it with the Flip View button. */
4053         flipView = appData.autoFlipView ?
4054           (newGameMode == IcsPlayingBlack) || ics_flip :
4055           appData.flipView;
4056
4057         /* Done with values from previous mode; copy in new ones */
4058         gameMode = newGameMode;
4059         ModeHighlight();
4060         ics_gamenum = gamenum;
4061         if (gamenum == gs_gamenum) {
4062             int klen = strlen(gs_kind);
4063             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4064             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4065             gameInfo.event = StrSave(str);
4066         } else {
4067             gameInfo.event = StrSave("ICS game");
4068         }
4069         gameInfo.site = StrSave(appData.icsHost);
4070         gameInfo.date = PGNDate();
4071         gameInfo.round = StrSave("-");
4072         gameInfo.white = StrSave(white);
4073         gameInfo.black = StrSave(black);
4074         timeControl = basetime * 60 * 1000;
4075         timeControl_2 = 0;
4076         timeIncrement = increment * 1000;
4077         movesPerSession = 0;
4078         gameInfo.timeControl = TimeControlTagValue();
4079         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4080   if (appData.debugMode) {
4081     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4082     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4083     setbuf(debugFP, NULL);
4084   }
4085
4086         gameInfo.outOfBook = NULL;
4087
4088         /* Do we have the ratings? */
4089         if (strcmp(player1Name, white) == 0 &&
4090             strcmp(player2Name, black) == 0) {
4091             if (appData.debugMode)
4092               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4093                       player1Rating, player2Rating);
4094             gameInfo.whiteRating = player1Rating;
4095             gameInfo.blackRating = player2Rating;
4096         } else if (strcmp(player2Name, white) == 0 &&
4097                    strcmp(player1Name, black) == 0) {
4098             if (appData.debugMode)
4099               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4100                       player2Rating, player1Rating);
4101             gameInfo.whiteRating = player2Rating;
4102             gameInfo.blackRating = player1Rating;
4103         }
4104         player1Name[0] = player2Name[0] = NULLCHAR;
4105
4106         /* Silence shouts if requested */
4107         if (appData.quietPlay &&
4108             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4109             SendToICS(ics_prefix);
4110             SendToICS("set shout 0\n");
4111         }
4112     }
4113
4114     /* Deal with midgame name changes */
4115     if (!newGame) {
4116         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4117             if (gameInfo.white) free(gameInfo.white);
4118             gameInfo.white = StrSave(white);
4119         }
4120         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4121             if (gameInfo.black) free(gameInfo.black);
4122             gameInfo.black = StrSave(black);
4123         }
4124     }
4125
4126     /* Throw away game result if anything actually changes in examine mode */
4127     if (gameMode == IcsExamining && !newGame) {
4128         gameInfo.result = GameUnfinished;
4129         if (gameInfo.resultDetails != NULL) {
4130             free(gameInfo.resultDetails);
4131             gameInfo.resultDetails = NULL;
4132         }
4133     }
4134
4135     /* In pausing && IcsExamining mode, we ignore boards coming
4136        in if they are in a different variation than we are. */
4137     if (pauseExamInvalid) return;
4138     if (pausing && gameMode == IcsExamining) {
4139         if (moveNum <= pauseExamForwardMostMove) {
4140             pauseExamInvalid = TRUE;
4141             forwardMostMove = pauseExamForwardMostMove;
4142             return;
4143         }
4144     }
4145
4146   if (appData.debugMode) {
4147     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4148   }
4149     /* Parse the board */
4150     for (k = 0; k < ranks; k++) {
4151       for (j = 0; j < files; j++)
4152         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4153       if(gameInfo.holdingsWidth > 1) {
4154            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4155            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4156       }
4157     }
4158     CopyBoard(boards[moveNum], board);
4159     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4160     if (moveNum == 0) {
4161         startedFromSetupPosition =
4162           !CompareBoards(board, initialPosition);
4163         if(startedFromSetupPosition)
4164             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4165     }
4166
4167     /* [HGM] Set castling rights. Take the outermost Rooks,
4168        to make it also work for FRC opening positions. Note that board12
4169        is really defective for later FRC positions, as it has no way to
4170        indicate which Rook can castle if they are on the same side of King.
4171        For the initial position we grant rights to the outermost Rooks,
4172        and remember thos rights, and we then copy them on positions
4173        later in an FRC game. This means WB might not recognize castlings with
4174        Rooks that have moved back to their original position as illegal,
4175        but in ICS mode that is not its job anyway.
4176     */
4177     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4178     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4179
4180         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4181             if(board[0][i] == WhiteRook) j = i;
4182         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4183         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4184             if(board[0][i] == WhiteRook) j = i;
4185         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4186         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4187             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4188         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4189         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4190             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4191         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4192
4193         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4194         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4195             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4196         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4197             if(board[BOARD_HEIGHT-1][k] == bKing)
4198                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4199         if(gameInfo.variant == VariantTwoKings) {
4200             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4201             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4202             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4203         }
4204     } else { int r;
4205         r = boards[moveNum][CASTLING][0] = initialRights[0];
4206         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4207         r = boards[moveNum][CASTLING][1] = initialRights[1];
4208         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4209         r = boards[moveNum][CASTLING][3] = initialRights[3];
4210         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4211         r = boards[moveNum][CASTLING][4] = initialRights[4];
4212         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4213         /* wildcastle kludge: always assume King has rights */
4214         r = boards[moveNum][CASTLING][2] = initialRights[2];
4215         r = boards[moveNum][CASTLING][5] = initialRights[5];
4216     }
4217     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4218     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4219
4220
4221     if (ics_getting_history == H_GOT_REQ_HEADER ||
4222         ics_getting_history == H_GOT_UNREQ_HEADER) {
4223         /* This was an initial position from a move list, not
4224            the current position */
4225         return;
4226     }
4227
4228     /* Update currentMove and known move number limits */
4229     newMove = newGame || moveNum > forwardMostMove;
4230
4231     if (newGame) {
4232         forwardMostMove = backwardMostMove = currentMove = moveNum;
4233         if (gameMode == IcsExamining && moveNum == 0) {
4234           /* Workaround for ICS limitation: we are not told the wild
4235              type when starting to examine a game.  But if we ask for
4236              the move list, the move list header will tell us */
4237             ics_getting_history = H_REQUESTED;
4238             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4239             SendToICS(str);
4240         }
4241     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4242                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4243 #if ZIPPY
4244         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4245         /* [HGM] applied this also to an engine that is silently watching        */
4246         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4247             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4248             gameInfo.variant == currentlyInitializedVariant) {
4249           takeback = forwardMostMove - moveNum;
4250           for (i = 0; i < takeback; i++) {
4251             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4252             SendToProgram("undo\n", &first);
4253           }
4254         }
4255 #endif
4256
4257         forwardMostMove = moveNum;
4258         if (!pausing || currentMove > forwardMostMove)
4259           currentMove = forwardMostMove;
4260     } else {
4261         /* New part of history that is not contiguous with old part */
4262         if (pausing && gameMode == IcsExamining) {
4263             pauseExamInvalid = TRUE;
4264             forwardMostMove = pauseExamForwardMostMove;
4265             return;
4266         }
4267         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4268 #if ZIPPY
4269             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4270                 // [HGM] when we will receive the move list we now request, it will be
4271                 // fed to the engine from the first move on. So if the engine is not
4272                 // in the initial position now, bring it there.
4273                 InitChessProgram(&first, 0);
4274             }
4275 #endif
4276             ics_getting_history = H_REQUESTED;
4277             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4278             SendToICS(str);
4279         }
4280         forwardMostMove = backwardMostMove = currentMove = moveNum;
4281     }
4282
4283     /* Update the clocks */
4284     if (strchr(elapsed_time, '.')) {
4285       /* Time is in ms */
4286       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4287       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4288     } else {
4289       /* Time is in seconds */
4290       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4291       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4292     }
4293
4294
4295 #if ZIPPY
4296     if (appData.zippyPlay && newGame &&
4297         gameMode != IcsObserving && gameMode != IcsIdle &&
4298         gameMode != IcsExamining)
4299       ZippyFirstBoard(moveNum, basetime, increment);
4300 #endif
4301
4302     /* Put the move on the move list, first converting
4303        to canonical algebraic form. */
4304     if (moveNum > 0) {
4305   if (appData.debugMode) {
4306     if (appData.debugMode) { int f = forwardMostMove;
4307         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4308                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4309                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4310     }
4311     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4312     fprintf(debugFP, "moveNum = %d\n", moveNum);
4313     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4314     setbuf(debugFP, NULL);
4315   }
4316         if (moveNum <= backwardMostMove) {
4317             /* We don't know what the board looked like before
4318                this move.  Punt. */
4319           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4320             strcat(parseList[moveNum - 1], " ");
4321             strcat(parseList[moveNum - 1], elapsed_time);
4322             moveList[moveNum - 1][0] = NULLCHAR;
4323         } else if (strcmp(move_str, "none") == 0) {
4324             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4325             /* Again, we don't know what the board looked like;
4326                this is really the start of the game. */
4327             parseList[moveNum - 1][0] = NULLCHAR;
4328             moveList[moveNum - 1][0] = NULLCHAR;
4329             backwardMostMove = moveNum;
4330             startedFromSetupPosition = TRUE;
4331             fromX = fromY = toX = toY = -1;
4332         } else {
4333           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4334           //                 So we parse the long-algebraic move string in stead of the SAN move
4335           int valid; char buf[MSG_SIZ], *prom;
4336
4337           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4338                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4339           // str looks something like "Q/a1-a2"; kill the slash
4340           if(str[1] == '/')
4341             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4342           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4343           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4344                 strcat(buf, prom); // long move lacks promo specification!
4345           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4346                 if(appData.debugMode)
4347                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4348                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4349           }
4350           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4351                                 &fromX, &fromY, &toX, &toY, &promoChar)
4352                || ParseOneMove(buf, moveNum - 1, &moveType,
4353                                 &fromX, &fromY, &toX, &toY, &promoChar);
4354           // end of long SAN patch
4355           if (valid) {
4356             (void) CoordsToAlgebraic(boards[moveNum - 1],
4357                                      PosFlags(moveNum - 1),
4358                                      fromY, fromX, toY, toX, promoChar,
4359                                      parseList[moveNum-1]);
4360             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4361               case MT_NONE:
4362               case MT_STALEMATE:
4363               default:
4364                 break;
4365               case MT_CHECK:
4366                 if(gameInfo.variant != VariantShogi)
4367                     strcat(parseList[moveNum - 1], "+");
4368                 break;
4369               case MT_CHECKMATE:
4370               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4371                 strcat(parseList[moveNum - 1], "#");
4372                 break;
4373             }
4374             strcat(parseList[moveNum - 1], " ");
4375             strcat(parseList[moveNum - 1], elapsed_time);
4376             /* currentMoveString is set as a side-effect of ParseOneMove */
4377             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4378             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4379             strcat(moveList[moveNum - 1], "\n");
4380
4381             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4382               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4383                 ChessSquare old, new = boards[moveNum][k][j];
4384                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4385                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4386                   if(old == new) continue;
4387                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4388                   else if(new == WhiteWazir || new == BlackWazir) {
4389                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4390                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4391                       else boards[moveNum][k][j] = old; // preserve type of Gold
4392                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4393                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4394               }
4395           } else {
4396             /* Move from ICS was illegal!?  Punt. */
4397             if (appData.debugMode) {
4398               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4399               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4400             }
4401             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4402             strcat(parseList[moveNum - 1], " ");
4403             strcat(parseList[moveNum - 1], elapsed_time);
4404             moveList[moveNum - 1][0] = NULLCHAR;
4405             fromX = fromY = toX = toY = -1;
4406           }
4407         }
4408   if (appData.debugMode) {
4409     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4410     setbuf(debugFP, NULL);
4411   }
4412
4413 #if ZIPPY
4414         /* Send move to chess program (BEFORE animating it). */
4415         if (appData.zippyPlay && !newGame && newMove &&
4416            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4417
4418             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4419                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4420                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4421                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4422                             move_str);
4423                     DisplayError(str, 0);
4424                 } else {
4425                     if (first.sendTime) {
4426                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4427                     }
4428                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4429                     if (firstMove && !bookHit) {
4430                         firstMove = FALSE;
4431                         if (first.useColors) {
4432                           SendToProgram(gameMode == IcsPlayingWhite ?
4433                                         "white\ngo\n" :
4434                                         "black\ngo\n", &first);
4435                         } else {
4436                           SendToProgram("go\n", &first);
4437                         }
4438                         first.maybeThinking = TRUE;
4439                     }
4440                 }
4441             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4442               if (moveList[moveNum - 1][0] == NULLCHAR) {
4443                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4444                 DisplayError(str, 0);
4445               } else {
4446                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4447                 SendMoveToProgram(moveNum - 1, &first);
4448               }
4449             }
4450         }
4451 #endif
4452     }
4453
4454     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4455         /* If move comes from a remote source, animate it.  If it
4456            isn't remote, it will have already been animated. */
4457         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4458             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4459         }
4460         if (!pausing && appData.highlightLastMove) {
4461             SetHighlights(fromX, fromY, toX, toY);
4462         }
4463     }
4464
4465     /* Start the clocks */
4466     whiteFlag = blackFlag = FALSE;
4467     appData.clockMode = !(basetime == 0 && increment == 0);
4468     if (ticking == 0) {
4469       ics_clock_paused = TRUE;
4470       StopClocks();
4471     } else if (ticking == 1) {
4472       ics_clock_paused = FALSE;
4473     }
4474     if (gameMode == IcsIdle ||
4475         relation == RELATION_OBSERVING_STATIC ||
4476         relation == RELATION_EXAMINING ||
4477         ics_clock_paused)
4478       DisplayBothClocks();
4479     else
4480       StartClocks();
4481
4482     /* Display opponents and material strengths */
4483     if (gameInfo.variant != VariantBughouse &&
4484         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4485         if (tinyLayout || smallLayout) {
4486             if(gameInfo.variant == VariantNormal)
4487               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4488                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4489                     basetime, increment);
4490             else
4491               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4492                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4493                     basetime, increment, (int) gameInfo.variant);
4494         } else {
4495             if(gameInfo.variant == VariantNormal)
4496               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4497                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4498                     basetime, increment);
4499             else
4500               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4501                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4502                     basetime, increment, VariantName(gameInfo.variant));
4503         }
4504         DisplayTitle(str);
4505   if (appData.debugMode) {
4506     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4507   }
4508     }
4509
4510
4511     /* Display the board */
4512     if (!pausing && !appData.noGUI) {
4513
4514       if (appData.premove)
4515           if (!gotPremove ||
4516              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4517              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4518               ClearPremoveHighlights();
4519
4520       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4521         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4522       DrawPosition(j, boards[currentMove]);
4523
4524       DisplayMove(moveNum - 1);
4525       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4526             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4527               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4528         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4529       }
4530     }
4531
4532     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4533 #if ZIPPY
4534     if(bookHit) { // [HGM] book: simulate book reply
4535         static char bookMove[MSG_SIZ]; // a bit generous?
4536
4537         programStats.nodes = programStats.depth = programStats.time =
4538         programStats.score = programStats.got_only_move = 0;
4539         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4540
4541         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4542         strcat(bookMove, bookHit);
4543         HandleMachineMove(bookMove, &first);
4544     }
4545 #endif
4546 }
4547
4548 void
4549 GetMoveListEvent()
4550 {
4551     char buf[MSG_SIZ];
4552     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4553         ics_getting_history = H_REQUESTED;
4554         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4555         SendToICS(buf);
4556     }
4557 }
4558
4559 void
4560 AnalysisPeriodicEvent(force)
4561      int force;
4562 {
4563     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4564          && !force) || !appData.periodicUpdates)
4565       return;
4566
4567     /* Send . command to Crafty to collect stats */
4568     SendToProgram(".\n", &first);
4569
4570     /* Don't send another until we get a response (this makes
4571        us stop sending to old Crafty's which don't understand
4572        the "." command (sending illegal cmds resets node count & time,
4573        which looks bad)) */
4574     programStats.ok_to_send = 0;
4575 }
4576
4577 void ics_update_width(new_width)
4578         int new_width;
4579 {
4580         ics_printf("set width %d\n", new_width);
4581 }
4582
4583 void
4584 SendMoveToProgram(moveNum, cps)
4585      int moveNum;
4586      ChessProgramState *cps;
4587 {
4588     char buf[MSG_SIZ];
4589
4590     if (cps->useUsermove) {
4591       SendToProgram("usermove ", cps);
4592     }
4593     if (cps->useSAN) {
4594       char *space;
4595       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4596         int len = space - parseList[moveNum];
4597         memcpy(buf, parseList[moveNum], len);
4598         buf[len++] = '\n';
4599         buf[len] = NULLCHAR;
4600       } else {
4601         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4602       }
4603       SendToProgram(buf, cps);
4604     } else {
4605       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4606         AlphaRank(moveList[moveNum], 4);
4607         SendToProgram(moveList[moveNum], cps);
4608         AlphaRank(moveList[moveNum], 4); // and back
4609       } else
4610       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4611        * the engine. It would be nice to have a better way to identify castle
4612        * moves here. */
4613       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4614                                                                          && cps->useOOCastle) {
4615         int fromX = moveList[moveNum][0] - AAA;
4616         int fromY = moveList[moveNum][1] - ONE;
4617         int toX = moveList[moveNum][2] - AAA;
4618         int toY = moveList[moveNum][3] - ONE;
4619         if((boards[moveNum][fromY][fromX] == WhiteKing
4620             && boards[moveNum][toY][toX] == WhiteRook)
4621            || (boards[moveNum][fromY][fromX] == BlackKing
4622                && boards[moveNum][toY][toX] == BlackRook)) {
4623           if(toX > fromX) SendToProgram("O-O\n", cps);
4624           else SendToProgram("O-O-O\n", cps);
4625         }
4626         else SendToProgram(moveList[moveNum], cps);
4627       }
4628       else SendToProgram(moveList[moveNum], cps);
4629       /* End of additions by Tord */
4630     }
4631
4632     /* [HGM] setting up the opening has brought engine in force mode! */
4633     /*       Send 'go' if we are in a mode where machine should play. */
4634     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4635         (gameMode == TwoMachinesPlay   ||
4636 #if ZIPPY
4637          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4638 #endif
4639          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4640         SendToProgram("go\n", cps);
4641   if (appData.debugMode) {
4642     fprintf(debugFP, "(extra)\n");
4643   }
4644     }
4645     setboardSpoiledMachineBlack = 0;
4646 }
4647
4648 void
4649 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4650      ChessMove moveType;
4651      int fromX, fromY, toX, toY;
4652      char promoChar;
4653 {
4654     char user_move[MSG_SIZ];
4655
4656     switch (moveType) {
4657       default:
4658         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4659                 (int)moveType, fromX, fromY, toX, toY);
4660         DisplayError(user_move + strlen("say "), 0);
4661         break;
4662       case WhiteKingSideCastle:
4663       case BlackKingSideCastle:
4664       case WhiteQueenSideCastleWild:
4665       case BlackQueenSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteHSideCastleFR:
4668       case BlackHSideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o\n");
4671         break;
4672       case WhiteQueenSideCastle:
4673       case BlackQueenSideCastle:
4674       case WhiteKingSideCastleWild:
4675       case BlackKingSideCastleWild:
4676       /* PUSH Fabien */
4677       case WhiteASideCastleFR:
4678       case BlackASideCastleFR:
4679       /* POP Fabien */
4680         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4681         break;
4682       case WhiteNonPromotion:
4683       case BlackNonPromotion:
4684         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4685         break;
4686       case WhitePromotion:
4687       case BlackPromotion:
4688         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4689           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4690                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4691                 PieceToChar(WhiteFerz));
4692         else if(gameInfo.variant == VariantGreat)
4693           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4694                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4695                 PieceToChar(WhiteMan));
4696         else
4697           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4698                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4699                 promoChar);
4700         break;
4701       case WhiteDrop:
4702       case BlackDrop:
4703       drop:
4704         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4705                  ToUpper(PieceToChar((ChessSquare) fromX)),
4706                  AAA + toX, ONE + toY);
4707         break;
4708       case IllegalMove:  /* could be a variant we don't quite understand */
4709         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4710       case NormalMove:
4711       case WhiteCapturesEnPassant:
4712       case BlackCapturesEnPassant:
4713         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4714                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4715         break;
4716     }
4717     SendToICS(user_move);
4718     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4719         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4720 }
4721
4722 void
4723 UploadGameEvent()
4724 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4725     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4726     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4727     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4728         DisplayError("You cannot do this while you are playing or observing", 0);
4729         return;
4730     }
4731     if(gameMode != IcsExamining) { // is this ever not the case?
4732         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4733
4734         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4735           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4736         } else { // on FICS we must first go to general examine mode
4737           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4738         }
4739         if(gameInfo.variant != VariantNormal) {
4740             // try figure out wild number, as xboard names are not always valid on ICS
4741             for(i=1; i<=36; i++) {
4742               snprintf(buf, MSG_SIZ, "wild/%d", i);
4743                 if(StringToVariant(buf) == gameInfo.variant) break;
4744             }
4745             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4746             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4747             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4748         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4749         SendToICS(ics_prefix);
4750         SendToICS(buf);
4751         if(startedFromSetupPosition || backwardMostMove != 0) {
4752           fen = PositionToFEN(backwardMostMove, NULL);
4753           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4754             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4755             SendToICS(buf);
4756           } else { // FICS: everything has to set by separate bsetup commands
4757             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4758             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4759             SendToICS(buf);
4760             if(!WhiteOnMove(backwardMostMove)) {
4761                 SendToICS("bsetup tomove black\n");
4762             }
4763             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4764             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4765             SendToICS(buf);
4766             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4767             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4768             SendToICS(buf);
4769             i = boards[backwardMostMove][EP_STATUS];
4770             if(i >= 0) { // set e.p.
4771               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4772                 SendToICS(buf);
4773             }
4774             bsetup++;
4775           }
4776         }
4777       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4778             SendToICS("bsetup done\n"); // switch to normal examining.
4779     }
4780     for(i = backwardMostMove; i<last; i++) {
4781         char buf[20];
4782         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4783         SendToICS(buf);
4784     }
4785     SendToICS(ics_prefix);
4786     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4787 }
4788
4789 void
4790 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4791      int rf, ff, rt, ft;
4792      char promoChar;
4793      char move[7];
4794 {
4795     if (rf == DROP_RANK) {
4796       sprintf(move, "%c@%c%c\n",
4797                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4798     } else {
4799         if (promoChar == 'x' || promoChar == NULLCHAR) {
4800           sprintf(move, "%c%c%c%c\n",
4801                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4802         } else {
4803             sprintf(move, "%c%c%c%c%c\n",
4804                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4805         }
4806     }
4807 }
4808
4809 void
4810 ProcessICSInitScript(f)
4811      FILE *f;
4812 {
4813     char buf[MSG_SIZ];
4814
4815     while (fgets(buf, MSG_SIZ, f)) {
4816         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4817     }
4818
4819     fclose(f);
4820 }
4821
4822
4823 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4824 void
4825 AlphaRank(char *move, int n)
4826 {
4827 //    char *p = move, c; int x, y;
4828
4829     if (appData.debugMode) {
4830         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4831     }
4832
4833     if(move[1]=='*' &&
4834        move[2]>='0' && move[2]<='9' &&
4835        move[3]>='a' && move[3]<='x'    ) {
4836         move[1] = '@';
4837         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4838         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4839     } else
4840     if(move[0]>='0' && move[0]<='9' &&
4841        move[1]>='a' && move[1]<='x' &&
4842        move[2]>='0' && move[2]<='9' &&
4843        move[3]>='a' && move[3]<='x'    ) {
4844         /* input move, Shogi -> normal */
4845         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4846         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4847         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4848         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4849     } else
4850     if(move[1]=='@' &&
4851        move[3]>='0' && move[3]<='9' &&
4852        move[2]>='a' && move[2]<='x'    ) {
4853         move[1] = '*';
4854         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4855         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4856     } else
4857     if(
4858        move[0]>='a' && move[0]<='x' &&
4859        move[3]>='0' && move[3]<='9' &&
4860        move[2]>='a' && move[2]<='x'    ) {
4861          /* output move, normal -> Shogi */
4862         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4863         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4864         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4865         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4866         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4867     }
4868     if (appData.debugMode) {
4869         fprintf(debugFP, "   out = '%s'\n", move);
4870     }
4871 }
4872
4873 char yy_textstr[8000];
4874
4875 /* Parser for moves from gnuchess, ICS, or user typein box */
4876 Boolean
4877 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4878      char *move;
4879      int moveNum;
4880      ChessMove *moveType;
4881      int *fromX, *fromY, *toX, *toY;
4882      char *promoChar;
4883 {
4884     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4885
4886     switch (*moveType) {
4887       case WhitePromotion:
4888       case BlackPromotion:
4889       case WhiteNonPromotion:
4890       case BlackNonPromotion:
4891       case NormalMove:
4892       case WhiteCapturesEnPassant:
4893       case BlackCapturesEnPassant:
4894       case WhiteKingSideCastle:
4895       case WhiteQueenSideCastle:
4896       case BlackKingSideCastle:
4897       case BlackQueenSideCastle:
4898       case WhiteKingSideCastleWild:
4899       case WhiteQueenSideCastleWild:
4900       case BlackKingSideCastleWild:
4901       case BlackQueenSideCastleWild:
4902       /* Code added by Tord: */
4903       case WhiteHSideCastleFR:
4904       case WhiteASideCastleFR:
4905       case BlackHSideCastleFR:
4906       case BlackASideCastleFR:
4907       /* End of code added by Tord */
4908       case IllegalMove:         /* bug or odd chess variant */
4909         *fromX = currentMoveString[0] - AAA;
4910         *fromY = currentMoveString[1] - ONE;
4911         *toX = currentMoveString[2] - AAA;
4912         *toY = currentMoveString[3] - ONE;
4913         *promoChar = currentMoveString[4];
4914         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4915             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4916     if (appData.debugMode) {
4917         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4918     }
4919             *fromX = *fromY = *toX = *toY = 0;
4920             return FALSE;
4921         }
4922         if (appData.testLegality) {
4923           return (*moveType != IllegalMove);
4924         } else {
4925           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4926                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4927         }
4928
4929       case WhiteDrop:
4930       case BlackDrop:
4931         *fromX = *moveType == WhiteDrop ?
4932           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4933           (int) CharToPiece(ToLower(currentMoveString[0]));
4934         *fromY = DROP_RANK;
4935         *toX = currentMoveString[2] - AAA;
4936         *toY = currentMoveString[3] - ONE;
4937         *promoChar = NULLCHAR;
4938         return TRUE;
4939
4940       case AmbiguousMove:
4941       case ImpossibleMove:
4942       case EndOfFile:
4943       case ElapsedTime:
4944       case Comment:
4945       case PGNTag:
4946       case NAG:
4947       case WhiteWins:
4948       case BlackWins:
4949       case GameIsDrawn:
4950       default:
4951     if (appData.debugMode) {
4952         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4953     }
4954         /* bug? */
4955         *fromX = *fromY = *toX = *toY = 0;
4956         *promoChar = NULLCHAR;
4957         return FALSE;
4958     }
4959 }
4960
4961
4962 void
4963 ParsePV(char *pv, Boolean storeComments)
4964 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4965   int fromX, fromY, toX, toY; char promoChar;
4966   ChessMove moveType;
4967   Boolean valid;
4968   int nr = 0;
4969
4970   endPV = forwardMostMove;
4971   do {
4972     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4973     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4974     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4975 if(appData.debugMode){
4976 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);
4977 }
4978     if(!valid && nr == 0 &&
4979        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4980         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4981         // Hande case where played move is different from leading PV move
4982         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4983         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4984         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4985         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4986           endPV += 2; // if position different, keep this
4987           moveList[endPV-1][0] = fromX + AAA;
4988           moveList[endPV-1][1] = fromY + ONE;
4989           moveList[endPV-1][2] = toX + AAA;
4990           moveList[endPV-1][3] = toY + ONE;
4991           parseList[endPV-1][0] = NULLCHAR;
4992           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4993         }
4994       }
4995     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4996     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4997     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4998     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4999         valid++; // allow comments in PV
5000         continue;
5001     }
5002     nr++;
5003     if(endPV+1 > framePtr) break; // no space, truncate
5004     if(!valid) break;
5005     endPV++;
5006     CopyBoard(boards[endPV], boards[endPV-1]);
5007     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5008     moveList[endPV-1][0] = fromX + AAA;
5009     moveList[endPV-1][1] = fromY + ONE;
5010     moveList[endPV-1][2] = toX + AAA;
5011     moveList[endPV-1][3] = toY + ONE;
5012     if(storeComments)
5013         CoordsToAlgebraic(boards[endPV - 1],
5014                              PosFlags(endPV - 1),
5015                              fromY, fromX, toY, toX, promoChar,
5016                              parseList[endPV - 1]);
5017     else
5018         parseList[endPV-1][0] = NULLCHAR;
5019   } while(valid);
5020   currentMove = endPV;
5021   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5022   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5023                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5024   DrawPosition(TRUE, boards[currentMove]);
5025 }
5026
5027 static int lastX, lastY;
5028
5029 Boolean
5030 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5031 {
5032         int startPV;
5033         char *p;
5034
5035         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5036         lastX = x; lastY = y;
5037         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5038         startPV = index;
5039         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5040         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5041         index = startPV;
5042         do{ while(buf[index] && buf[index] != '\n') index++;
5043         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5044         buf[index] = 0;
5045         ParsePV(buf+startPV, FALSE);
5046         *start = startPV; *end = index-1;
5047         return TRUE;
5048 }
5049
5050 Boolean
5051 LoadPV(int x, int y)
5052 { // called on right mouse click to load PV
5053   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5054   lastX = x; lastY = y;
5055   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5056   return TRUE;
5057 }
5058
5059 void
5060 UnLoadPV()
5061 {
5062   if(endPV < 0) return;
5063   endPV = -1;
5064   currentMove = forwardMostMove;
5065   ClearPremoveHighlights();
5066   DrawPosition(TRUE, boards[currentMove]);
5067 }
5068
5069 void
5070 MovePV(int x, int y, int h)
5071 { // step through PV based on mouse coordinates (called on mouse move)
5072   int margin = h>>3, step = 0;
5073
5074   if(endPV < 0) return;
5075   // we must somehow check if right button is still down (might be released off board!)
5076   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5077   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5078   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5079   if(!step) return;
5080   lastX = x; lastY = y;
5081   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5082   currentMove += step;
5083   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5084   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5085                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5086   DrawPosition(FALSE, boards[currentMove]);
5087 }
5088
5089
5090 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5091 // All positions will have equal probability, but the current method will not provide a unique
5092 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5093 #define DARK 1
5094 #define LITE 2
5095 #define ANY 3
5096
5097 int squaresLeft[4];
5098 int piecesLeft[(int)BlackPawn];
5099 int seed, nrOfShuffles;
5100
5101 void GetPositionNumber()
5102 {       // sets global variable seed
5103         int i;
5104
5105         seed = appData.defaultFrcPosition;
5106         if(seed < 0) { // randomize based on time for negative FRC position numbers
5107                 for(i=0; i<50; i++) seed += random();
5108                 seed = random() ^ random() >> 8 ^ random() << 8;
5109                 if(seed<0) seed = -seed;
5110         }
5111 }
5112
5113 int put(Board board, int pieceType, int rank, int n, int shade)
5114 // put the piece on the (n-1)-th empty squares of the given shade
5115 {
5116         int i;
5117
5118         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5119                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5120                         board[rank][i] = (ChessSquare) pieceType;
5121                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5122                         squaresLeft[ANY]--;
5123                         piecesLeft[pieceType]--;
5124                         return i;
5125                 }
5126         }
5127         return -1;
5128 }
5129
5130
5131 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5132 // calculate where the next piece goes, (any empty square), and put it there
5133 {
5134         int i;
5135
5136         i = seed % squaresLeft[shade];
5137         nrOfShuffles *= squaresLeft[shade];
5138         seed /= squaresLeft[shade];
5139         put(board, pieceType, rank, i, shade);
5140 }
5141
5142 void AddTwoPieces(Board board, int pieceType, int rank)
5143 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5144 {
5145         int i, n=squaresLeft[ANY], j=n-1, k;
5146
5147         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5148         i = seed % k;  // pick one
5149         nrOfShuffles *= k;
5150         seed /= k;
5151         while(i >= j) i -= j--;
5152         j = n - 1 - j; i += j;
5153         put(board, pieceType, rank, j, ANY);
5154         put(board, pieceType, rank, i, ANY);
5155 }
5156
5157 void SetUpShuffle(Board board, int number)
5158 {
5159         int i, p, first=1;
5160
5161         GetPositionNumber(); nrOfShuffles = 1;
5162
5163         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5164         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5165         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5166
5167         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5168
5169         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5170             p = (int) board[0][i];
5171             if(p < (int) BlackPawn) piecesLeft[p] ++;
5172             board[0][i] = EmptySquare;
5173         }
5174
5175         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5176             // shuffles restricted to allow normal castling put KRR first
5177             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5178                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5179             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5180                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5181             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5182                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5183             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5184                 put(board, WhiteRook, 0, 0, ANY);
5185             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5186         }
5187
5188         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5189             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5190             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5191                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5192                 while(piecesLeft[p] >= 2) {
5193                     AddOnePiece(board, p, 0, LITE);
5194                     AddOnePiece(board, p, 0, DARK);
5195                 }
5196                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5197             }
5198
5199         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5200             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5201             // but we leave King and Rooks for last, to possibly obey FRC restriction
5202             if(p == (int)WhiteRook) continue;
5203             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5204             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5205         }
5206
5207         // now everything is placed, except perhaps King (Unicorn) and Rooks
5208
5209         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5210             // Last King gets castling rights
5211             while(piecesLeft[(int)WhiteUnicorn]) {
5212                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5213                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5214             }
5215
5216             while(piecesLeft[(int)WhiteKing]) {
5217                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5218                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5219             }
5220
5221
5222         } else {
5223             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5224             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5225         }
5226
5227         // Only Rooks can be left; simply place them all
5228         while(piecesLeft[(int)WhiteRook]) {
5229                 i = put(board, WhiteRook, 0, 0, ANY);
5230                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5231                         if(first) {
5232                                 first=0;
5233                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5234                         }
5235                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5236                 }
5237         }
5238         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5239             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5240         }
5241
5242         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5243 }
5244
5245 int SetCharTable( char *table, const char * map )
5246 /* [HGM] moved here from winboard.c because of its general usefulness */
5247 /*       Basically a safe strcpy that uses the last character as King */
5248 {
5249     int result = FALSE; int NrPieces;
5250
5251     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5252                     && NrPieces >= 12 && !(NrPieces&1)) {
5253         int i; /* [HGM] Accept even length from 12 to 34 */
5254
5255         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5256         for( i=0; i<NrPieces/2-1; i++ ) {
5257             table[i] = map[i];
5258             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5259         }
5260         table[(int) WhiteKing]  = map[NrPieces/2-1];
5261         table[(int) BlackKing]  = map[NrPieces-1];
5262
5263         result = TRUE;
5264     }
5265
5266     return result;
5267 }
5268
5269 void Prelude(Board board)
5270 {       // [HGM] superchess: random selection of exo-pieces
5271         int i, j, k; ChessSquare p;
5272         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5273
5274         GetPositionNumber(); // use FRC position number
5275
5276         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5277             SetCharTable(pieceToChar, appData.pieceToCharTable);
5278             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5279                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5280         }
5281
5282         j = seed%4;                 seed /= 4;
5283         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5284         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5285         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5286         j = seed%3 + (seed%3 >= j); seed /= 3;
5287         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5288         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5289         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5290         j = seed%3;                 seed /= 3;
5291         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5292         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5293         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5294         j = seed%2 + (seed%2 >= j); seed /= 2;
5295         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5296         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5297         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5298         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5299         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5300         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5301         put(board, exoPieces[0],    0, 0, ANY);
5302         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5303 }
5304
5305 void
5306 InitPosition(redraw)
5307      int redraw;
5308 {
5309     ChessSquare (* pieces)[BOARD_FILES];
5310     int i, j, pawnRow, overrule,
5311     oldx = gameInfo.boardWidth,
5312     oldy = gameInfo.boardHeight,
5313     oldh = gameInfo.holdingsWidth,
5314     oldv = gameInfo.variant;
5315
5316     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5317
5318     /* [AS] Initialize pv info list [HGM] and game status */
5319     {
5320         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5321             pvInfoList[i].depth = 0;
5322             boards[i][EP_STATUS] = EP_NONE;
5323             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5324         }
5325
5326         initialRulePlies = 0; /* 50-move counter start */
5327
5328         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5329         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5330     }
5331
5332
5333     /* [HGM] logic here is completely changed. In stead of full positions */
5334     /* the initialized data only consist of the two backranks. The switch */
5335     /* selects which one we will use, which is than copied to the Board   */
5336     /* initialPosition, which for the rest is initialized by Pawns and    */
5337     /* empty squares. This initial position is then copied to boards[0],  */
5338     /* possibly after shuffling, so that it remains available.            */
5339
5340     gameInfo.holdingsWidth = 0; /* default board sizes */
5341     gameInfo.boardWidth    = 8;
5342     gameInfo.boardHeight   = 8;
5343     gameInfo.holdingsSize  = 0;
5344     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5345     for(i=0; i<BOARD_FILES-2; i++)
5346       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5347     initialPosition[EP_STATUS] = EP_NONE;
5348     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5349     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5350          SetCharTable(pieceNickName, appData.pieceNickNames);
5351     else SetCharTable(pieceNickName, "............");
5352     pieces = FIDEArray;
5353
5354     switch (gameInfo.variant) {
5355     case VariantFischeRandom:
5356       shuffleOpenings = TRUE;
5357     default:
5358       break;
5359     case VariantShatranj:
5360       pieces = ShatranjArray;
5361       nrCastlingRights = 0;
5362       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5363       break;
5364     case VariantMakruk:
5365       pieces = makrukArray;
5366       nrCastlingRights = 0;
5367       startedFromSetupPosition = TRUE;
5368       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5369       break;
5370     case VariantTwoKings:
5371       pieces = twoKingsArray;
5372       break;
5373     case VariantCapaRandom:
5374       shuffleOpenings = TRUE;
5375     case VariantCapablanca:
5376       pieces = CapablancaArray;
5377       gameInfo.boardWidth = 10;
5378       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5379       break;
5380     case VariantGothic:
5381       pieces = GothicArray;
5382       gameInfo.boardWidth = 10;
5383       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5384       break;
5385     case VariantSChess:
5386       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5387       gameInfo.holdingsSize = 7;
5388       break;
5389     case VariantJanus:
5390       pieces = JanusArray;
5391       gameInfo.boardWidth = 10;
5392       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5393       nrCastlingRights = 6;
5394         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5395         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5396         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5397         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5398         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5399         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5400       break;
5401     case VariantFalcon:
5402       pieces = FalconArray;
5403       gameInfo.boardWidth = 10;
5404       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5405       break;
5406     case VariantXiangqi:
5407       pieces = XiangqiArray;
5408       gameInfo.boardWidth  = 9;
5409       gameInfo.boardHeight = 10;
5410       nrCastlingRights = 0;
5411       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5412       break;
5413     case VariantShogi:
5414       pieces = ShogiArray;
5415       gameInfo.boardWidth  = 9;
5416       gameInfo.boardHeight = 9;
5417       gameInfo.holdingsSize = 7;
5418       nrCastlingRights = 0;
5419       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5420       break;
5421     case VariantCourier:
5422       pieces = CourierArray;
5423       gameInfo.boardWidth  = 12;
5424       nrCastlingRights = 0;
5425       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5426       break;
5427     case VariantKnightmate:
5428       pieces = KnightmateArray;
5429       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5430       break;
5431     case VariantFairy:
5432       pieces = fairyArray;
5433       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5434       break;
5435     case VariantGreat:
5436       pieces = GreatArray;
5437       gameInfo.boardWidth = 10;
5438       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5439       gameInfo.holdingsSize = 8;
5440       break;
5441     case VariantSuper:
5442       pieces = FIDEArray;
5443       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5444       gameInfo.holdingsSize = 8;
5445       startedFromSetupPosition = TRUE;
5446       break;
5447     case VariantCrazyhouse:
5448     case VariantBughouse:
5449       pieces = FIDEArray;
5450       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5451       gameInfo.holdingsSize = 5;
5452       break;
5453     case VariantWildCastle:
5454       pieces = FIDEArray;
5455       /* !!?shuffle with kings guaranteed to be on d or e file */
5456       shuffleOpenings = 1;
5457       break;
5458     case VariantNoCastle:
5459       pieces = FIDEArray;
5460       nrCastlingRights = 0;
5461       /* !!?unconstrained back-rank shuffle */
5462       shuffleOpenings = 1;
5463       break;
5464     }
5465
5466     overrule = 0;
5467     if(appData.NrFiles >= 0) {
5468         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5469         gameInfo.boardWidth = appData.NrFiles;
5470     }
5471     if(appData.NrRanks >= 0) {
5472         gameInfo.boardHeight = appData.NrRanks;
5473     }
5474     if(appData.holdingsSize >= 0) {
5475         i = appData.holdingsSize;
5476         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5477         gameInfo.holdingsSize = i;
5478     }
5479     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5480     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5481         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5482
5483     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5484     if(pawnRow < 1) pawnRow = 1;
5485     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5486
5487     /* User pieceToChar list overrules defaults */
5488     if(appData.pieceToCharTable != NULL)
5489         SetCharTable(pieceToChar, appData.pieceToCharTable);
5490
5491     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5492
5493         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5494             s = (ChessSquare) 0; /* account holding counts in guard band */
5495         for( i=0; i<BOARD_HEIGHT; i++ )
5496             initialPosition[i][j] = s;
5497
5498         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5499         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5500         initialPosition[pawnRow][j] = WhitePawn;
5501         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5502         if(gameInfo.variant == VariantXiangqi) {
5503             if(j&1) {
5504                 initialPosition[pawnRow][j] =
5505                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5506                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5507                    initialPosition[2][j] = WhiteCannon;
5508                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5509                 }
5510             }
5511         }
5512         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5513     }
5514     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5515
5516             j=BOARD_LEFT+1;
5517             initialPosition[1][j] = WhiteBishop;
5518             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5519             j=BOARD_RGHT-2;
5520             initialPosition[1][j] = WhiteRook;
5521             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5522     }
5523
5524     if( nrCastlingRights == -1) {
5525         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5526         /*       This sets default castling rights from none to normal corners   */
5527         /* Variants with other castling rights must set them themselves above    */
5528         nrCastlingRights = 6;
5529
5530         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5531         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5532         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5533         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5534         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5535         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5536      }
5537
5538      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5539      if(gameInfo.variant == VariantGreat) { // promotion commoners
5540         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5541         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5542         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5543         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5544      }
5545      if( gameInfo.variant == VariantSChess ) {
5546       initialPosition[1][0] = BlackMarshall;
5547       initialPosition[2][0] = BlackAngel;
5548       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5549       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5550       initialPosition[1][1] = initialPosition[2][1] = 
5551       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5552      }
5553   if (appData.debugMode) {
5554     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5555   }
5556     if(shuffleOpenings) {
5557         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5558         startedFromSetupPosition = TRUE;
5559     }
5560     if(startedFromPositionFile) {
5561       /* [HGM] loadPos: use PositionFile for every new game */
5562       CopyBoard(initialPosition, filePosition);
5563       for(i=0; i<nrCastlingRights; i++)
5564           initialRights[i] = filePosition[CASTLING][i];
5565       startedFromSetupPosition = TRUE;
5566     }
5567
5568     CopyBoard(boards[0], initialPosition);
5569
5570     if(oldx != gameInfo.boardWidth ||
5571        oldy != gameInfo.boardHeight ||
5572        oldh != gameInfo.holdingsWidth
5573 #ifdef GOTHIC
5574        || oldv == VariantGothic ||        // For licensing popups
5575        gameInfo.variant == VariantGothic
5576 #endif
5577 #ifdef FALCON
5578        || oldv == VariantFalcon ||
5579        gameInfo.variant == VariantFalcon
5580 #endif
5581                                          )
5582             InitDrawingSizes(-2 ,0);
5583
5584     if (redraw)
5585       DrawPosition(TRUE, boards[currentMove]);
5586 }
5587
5588 void
5589 SendBoard(cps, moveNum)
5590      ChessProgramState *cps;
5591      int moveNum;
5592 {
5593     char message[MSG_SIZ];
5594
5595     if (cps->useSetboard) {
5596       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5597       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5598       SendToProgram(message, cps);
5599       free(fen);
5600
5601     } else {
5602       ChessSquare *bp;
5603       int i, j;
5604       /* Kludge to set black to move, avoiding the troublesome and now
5605        * deprecated "black" command.
5606        */
5607       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5608         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5609
5610       SendToProgram("edit\n", cps);
5611       SendToProgram("#\n", cps);
5612       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5613         bp = &boards[moveNum][i][BOARD_LEFT];
5614         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5615           if ((int) *bp < (int) BlackPawn) {
5616             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5617                     AAA + j, ONE + i);
5618             if(message[0] == '+' || message[0] == '~') {
5619               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5620                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5621                         AAA + j, ONE + i);
5622             }
5623             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5624                 message[1] = BOARD_RGHT   - 1 - j + '1';
5625                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5626             }
5627             SendToProgram(message, cps);
5628           }
5629         }
5630       }
5631
5632       SendToProgram("c\n", cps);
5633       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5634         bp = &boards[moveNum][i][BOARD_LEFT];
5635         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5636           if (((int) *bp != (int) EmptySquare)
5637               && ((int) *bp >= (int) BlackPawn)) {
5638             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5639                     AAA + j, ONE + i);
5640             if(message[0] == '+' || message[0] == '~') {
5641               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5642                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5643                         AAA + j, ONE + i);
5644             }
5645             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5646                 message[1] = BOARD_RGHT   - 1 - j + '1';
5647                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5648             }
5649             SendToProgram(message, cps);
5650           }
5651         }
5652       }
5653
5654       SendToProgram(".\n", cps);
5655     }
5656     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5657 }
5658
5659 static int autoQueen; // [HGM] oneclick
5660
5661 int
5662 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5663 {
5664     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5665     /* [HGM] add Shogi promotions */
5666     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5667     ChessSquare piece;
5668     ChessMove moveType;
5669     Boolean premove;
5670
5671     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5672     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5673
5674     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5675       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5676         return FALSE;
5677
5678     piece = boards[currentMove][fromY][fromX];
5679     if(gameInfo.variant == VariantShogi) {
5680         promotionZoneSize = BOARD_HEIGHT/3;
5681         highestPromotingPiece = (int)WhiteFerz;
5682     } else if(gameInfo.variant == VariantMakruk) {
5683         promotionZoneSize = 3;
5684     }
5685
5686     // next weed out all moves that do not touch the promotion zone at all
5687     if((int)piece >= BlackPawn) {
5688         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5689              return FALSE;
5690         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5691     } else {
5692         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5693            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5694     }
5695
5696     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5697
5698     // weed out mandatory Shogi promotions
5699     if(gameInfo.variant == VariantShogi) {
5700         if(piece >= BlackPawn) {
5701             if(toY == 0 && piece == BlackPawn ||
5702                toY == 0 && piece == BlackQueen ||
5703                toY <= 1 && piece == BlackKnight) {
5704                 *promoChoice = '+';
5705                 return FALSE;
5706             }
5707         } else {
5708             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5709                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5710                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5711                 *promoChoice = '+';
5712                 return FALSE;
5713             }
5714         }
5715     }
5716
5717     // weed out obviously illegal Pawn moves
5718     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5719         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5720         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5721         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5722         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5723         // note we are not allowed to test for valid (non-)capture, due to premove
5724     }
5725
5726     // we either have a choice what to promote to, or (in Shogi) whether to promote
5727     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5728         *promoChoice = PieceToChar(BlackFerz);  // no choice
5729         return FALSE;
5730     }
5731     // no sense asking what we must promote to if it is going to explode...
5732     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5733         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5734         return FALSE;
5735     }
5736     if(autoQueen) { // predetermined
5737         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5738              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5739         else *promoChoice = PieceToChar(BlackQueen);
5740         return FALSE;
5741     }
5742
5743     // suppress promotion popup on illegal moves that are not premoves
5744     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5745               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5746     if(appData.testLegality && !premove) {
5747         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5748                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5749         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5750             return FALSE;
5751     }
5752
5753     return TRUE;
5754 }
5755
5756 int
5757 InPalace(row, column)
5758      int row, column;
5759 {   /* [HGM] for Xiangqi */
5760     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5761          column < (BOARD_WIDTH + 4)/2 &&
5762          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5763     return FALSE;
5764 }
5765
5766 int
5767 PieceForSquare (x, y)
5768      int x;
5769      int y;
5770 {
5771   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5772      return -1;
5773   else
5774      return boards[currentMove][y][x];
5775 }
5776
5777 int
5778 OKToStartUserMove(x, y)
5779      int x, y;
5780 {
5781     ChessSquare from_piece;
5782     int white_piece;
5783
5784     if (matchMode) return FALSE;
5785     if (gameMode == EditPosition) return TRUE;
5786
5787     if (x >= 0 && y >= 0)
5788       from_piece = boards[currentMove][y][x];
5789     else
5790       from_piece = EmptySquare;
5791
5792     if (from_piece == EmptySquare) return FALSE;
5793
5794     white_piece = (int)from_piece >= (int)WhitePawn &&
5795       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5796
5797     switch (gameMode) {
5798       case PlayFromGameFile:
5799       case AnalyzeFile:
5800       case TwoMachinesPlay:
5801       case EndOfGame:
5802         return FALSE;
5803
5804       case IcsObserving:
5805       case IcsIdle:
5806         return FALSE;
5807
5808       case MachinePlaysWhite:
5809       case IcsPlayingBlack:
5810         if (appData.zippyPlay) return FALSE;
5811         if (white_piece) {
5812             DisplayMoveError(_("You are playing Black"));
5813             return FALSE;
5814         }
5815         break;
5816
5817       case MachinePlaysBlack:
5818       case IcsPlayingWhite:
5819         if (appData.zippyPlay) return FALSE;
5820         if (!white_piece) {
5821             DisplayMoveError(_("You are playing White"));
5822             return FALSE;
5823         }
5824         break;
5825
5826       case EditGame:
5827         if (!white_piece && WhiteOnMove(currentMove)) {
5828             DisplayMoveError(_("It is White's turn"));
5829             return FALSE;
5830         }
5831         if (white_piece && !WhiteOnMove(currentMove)) {
5832             DisplayMoveError(_("It is Black's turn"));
5833             return FALSE;
5834         }
5835         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5836             /* Editing correspondence game history */
5837             /* Could disallow this or prompt for confirmation */
5838             cmailOldMove = -1;
5839         }
5840         break;
5841
5842       case BeginningOfGame:
5843         if (appData.icsActive) return FALSE;
5844         if (!appData.noChessProgram) {
5845             if (!white_piece) {
5846                 DisplayMoveError(_("You are playing White"));
5847                 return FALSE;
5848             }
5849         }
5850         break;
5851
5852       case Training:
5853         if (!white_piece && WhiteOnMove(currentMove)) {
5854             DisplayMoveError(_("It is White's turn"));
5855             return FALSE;
5856         }
5857         if (white_piece && !WhiteOnMove(currentMove)) {
5858             DisplayMoveError(_("It is Black's turn"));
5859             return FALSE;
5860         }
5861         break;
5862
5863       default:
5864       case IcsExamining:
5865         break;
5866     }
5867     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5868         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5869         && gameMode != AnalyzeFile && gameMode != Training) {
5870         DisplayMoveError(_("Displayed position is not current"));
5871         return FALSE;
5872     }
5873     return TRUE;
5874 }
5875
5876 Boolean
5877 OnlyMove(int *x, int *y, Boolean captures) {
5878     DisambiguateClosure cl;
5879     if (appData.zippyPlay) return FALSE;
5880     switch(gameMode) {
5881       case MachinePlaysBlack:
5882       case IcsPlayingWhite:
5883       case BeginningOfGame:
5884         if(!WhiteOnMove(currentMove)) return FALSE;
5885         break;
5886       case MachinePlaysWhite:
5887       case IcsPlayingBlack:
5888         if(WhiteOnMove(currentMove)) return FALSE;
5889         break;
5890       case EditGame:
5891         break;
5892       default:
5893         return FALSE;
5894     }
5895     cl.pieceIn = EmptySquare;
5896     cl.rfIn = *y;
5897     cl.ffIn = *x;
5898     cl.rtIn = -1;
5899     cl.ftIn = -1;
5900     cl.promoCharIn = NULLCHAR;
5901     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5902     if( cl.kind == NormalMove ||
5903         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5904         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5905         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5906       fromX = cl.ff;
5907       fromY = cl.rf;
5908       *x = cl.ft;
5909       *y = cl.rt;
5910       return TRUE;
5911     }
5912     if(cl.kind != ImpossibleMove) return FALSE;
5913     cl.pieceIn = EmptySquare;
5914     cl.rfIn = -1;
5915     cl.ffIn = -1;
5916     cl.rtIn = *y;
5917     cl.ftIn = *x;
5918     cl.promoCharIn = NULLCHAR;
5919     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5920     if( cl.kind == NormalMove ||
5921         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5922         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5923         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5924       fromX = cl.ff;
5925       fromY = cl.rf;
5926       *x = cl.ft;
5927       *y = cl.rt;
5928       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5929       return TRUE;
5930     }
5931     return FALSE;
5932 }
5933
5934 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5935 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5936 int lastLoadGameUseList = FALSE;
5937 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5938 ChessMove lastLoadGameStart = EndOfFile;
5939
5940 void
5941 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5942      int fromX, fromY, toX, toY;
5943      int promoChar;
5944 {
5945     ChessMove moveType;
5946     ChessSquare pdown, pup;
5947
5948     /* Check if the user is playing in turn.  This is complicated because we
5949        let the user "pick up" a piece before it is his turn.  So the piece he
5950        tried to pick up may have been captured by the time he puts it down!
5951        Therefore we use the color the user is supposed to be playing in this
5952        test, not the color of the piece that is currently on the starting
5953        square---except in EditGame mode, where the user is playing both
5954        sides; fortunately there the capture race can't happen.  (It can
5955        now happen in IcsExamining mode, but that's just too bad.  The user
5956        will get a somewhat confusing message in that case.)
5957        */
5958
5959     switch (gameMode) {
5960       case PlayFromGameFile:
5961       case AnalyzeFile:
5962       case TwoMachinesPlay:
5963       case EndOfGame:
5964       case IcsObserving:
5965       case IcsIdle:
5966         /* We switched into a game mode where moves are not accepted,
5967            perhaps while the mouse button was down. */
5968         return;
5969
5970       case MachinePlaysWhite:
5971         /* User is moving for Black */
5972         if (WhiteOnMove(currentMove)) {
5973             DisplayMoveError(_("It is White's turn"));
5974             return;
5975         }
5976         break;
5977
5978       case MachinePlaysBlack:
5979         /* User is moving for White */
5980         if (!WhiteOnMove(currentMove)) {
5981             DisplayMoveError(_("It is Black's turn"));
5982             return;
5983         }
5984         break;
5985
5986       case EditGame:
5987       case IcsExamining:
5988       case BeginningOfGame:
5989       case AnalyzeMode:
5990       case Training:
5991         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5992             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5993             /* User is moving for Black */
5994             if (WhiteOnMove(currentMove)) {
5995                 DisplayMoveError(_("It is White's turn"));
5996                 return;
5997             }
5998         } else {
5999             /* User is moving for White */
6000             if (!WhiteOnMove(currentMove)) {
6001                 DisplayMoveError(_("It is Black's turn"));
6002                 return;
6003             }
6004         }
6005         break;
6006
6007       case IcsPlayingBlack:
6008         /* User is moving for Black */
6009         if (WhiteOnMove(currentMove)) {
6010             if (!appData.premove) {
6011                 DisplayMoveError(_("It is White's turn"));
6012             } else if (toX >= 0 && toY >= 0) {
6013                 premoveToX = toX;
6014                 premoveToY = toY;
6015                 premoveFromX = fromX;
6016                 premoveFromY = fromY;
6017                 premovePromoChar = promoChar;
6018                 gotPremove = 1;
6019                 if (appData.debugMode)
6020                     fprintf(debugFP, "Got premove: fromX %d,"
6021                             "fromY %d, toX %d, toY %d\n",
6022                             fromX, fromY, toX, toY);
6023             }
6024             return;
6025         }
6026         break;
6027
6028       case IcsPlayingWhite:
6029         /* User is moving for White */
6030         if (!WhiteOnMove(currentMove)) {
6031             if (!appData.premove) {
6032                 DisplayMoveError(_("It is Black's turn"));
6033             } else if (toX >= 0 && toY >= 0) {
6034                 premoveToX = toX;
6035                 premoveToY = toY;
6036                 premoveFromX = fromX;
6037                 premoveFromY = fromY;
6038                 premovePromoChar = promoChar;
6039                 gotPremove = 1;
6040                 if (appData.debugMode)
6041                     fprintf(debugFP, "Got premove: fromX %d,"
6042                             "fromY %d, toX %d, toY %d\n",
6043                             fromX, fromY, toX, toY);
6044             }
6045             return;
6046         }
6047         break;
6048
6049       default:
6050         break;
6051
6052       case EditPosition:
6053         /* EditPosition, empty square, or different color piece;
6054            click-click move is possible */
6055         if (toX == -2 || toY == -2) {
6056             boards[0][fromY][fromX] = EmptySquare;
6057             DrawPosition(FALSE, boards[currentMove]);
6058             return;
6059         } else if (toX >= 0 && toY >= 0) {
6060             boards[0][toY][toX] = boards[0][fromY][fromX];
6061             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6062                 if(boards[0][fromY][0] != EmptySquare) {
6063                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6064                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6065                 }
6066             } else
6067             if(fromX == BOARD_RGHT+1) {
6068                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6069                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6070                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6071                 }
6072             } else
6073             boards[0][fromY][fromX] = EmptySquare;
6074             DrawPosition(FALSE, boards[currentMove]);
6075             return;
6076         }
6077         return;
6078     }
6079
6080     if(toX < 0 || toY < 0) return;
6081     pdown = boards[currentMove][fromY][fromX];
6082     pup = boards[currentMove][toY][toX];
6083
6084     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6085     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6086          if( pup != EmptySquare ) return;
6087          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6088            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6089                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6090            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6091            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6092            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6093            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6094          fromY = DROP_RANK;
6095     }
6096
6097     /* [HGM] always test for legality, to get promotion info */
6098     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6099                                          fromY, fromX, toY, toX, promoChar);
6100     /* [HGM] but possibly ignore an IllegalMove result */
6101     if (appData.testLegality) {
6102         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6103             DisplayMoveError(_("Illegal move"));
6104             return;
6105         }
6106     }
6107
6108     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6109 }
6110
6111 /* Common tail of UserMoveEvent and DropMenuEvent */
6112 int
6113 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6114      ChessMove moveType;
6115      int fromX, fromY, toX, toY;
6116      /*char*/int promoChar;
6117 {
6118     char *bookHit = 0;
6119
6120     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6121         // [HGM] superchess: suppress promotions to non-available piece
6122         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6123         if(WhiteOnMove(currentMove)) {
6124             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6125         } else {
6126             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6127         }
6128     }
6129
6130     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6131        move type in caller when we know the move is a legal promotion */
6132     if(moveType == NormalMove && promoChar)
6133         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6134
6135     /* [HGM] <popupFix> The following if has been moved here from
6136        UserMoveEvent(). Because it seemed to belong here (why not allow
6137        piece drops in training games?), and because it can only be
6138        performed after it is known to what we promote. */
6139     if (gameMode == Training) {
6140       /* compare the move played on the board to the next move in the
6141        * game. If they match, display the move and the opponent's response.
6142        * If they don't match, display an error message.
6143        */
6144       int saveAnimate;
6145       Board testBoard;
6146       CopyBoard(testBoard, boards[currentMove]);
6147       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6148
6149       if (CompareBoards(testBoard, boards[currentMove+1])) {
6150         ForwardInner(currentMove+1);
6151
6152         /* Autoplay the opponent's response.
6153          * if appData.animate was TRUE when Training mode was entered,
6154          * the response will be animated.
6155          */
6156         saveAnimate = appData.animate;
6157         appData.animate = animateTraining;
6158         ForwardInner(currentMove+1);
6159         appData.animate = saveAnimate;
6160
6161         /* check for the end of the game */
6162         if (currentMove >= forwardMostMove) {
6163           gameMode = PlayFromGameFile;
6164           ModeHighlight();
6165           SetTrainingModeOff();
6166           DisplayInformation(_("End of game"));
6167         }
6168       } else {
6169         DisplayError(_("Incorrect move"), 0);
6170       }
6171       return 1;
6172     }
6173
6174   /* Ok, now we know that the move is good, so we can kill
6175      the previous line in Analysis Mode */
6176   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6177                                 && currentMove < forwardMostMove) {
6178     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6179     else forwardMostMove = currentMove;
6180   }
6181
6182   /* If we need the chess program but it's dead, restart it */
6183   ResurrectChessProgram();
6184
6185   /* A user move restarts a paused game*/
6186   if (pausing)
6187     PauseEvent();
6188
6189   thinkOutput[0] = NULLCHAR;
6190
6191   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6192
6193   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6194
6195   if (gameMode == BeginningOfGame) {
6196     if (appData.noChessProgram) {
6197       gameMode = EditGame;
6198       SetGameInfo();
6199     } else {
6200       char buf[MSG_SIZ];
6201       gameMode = MachinePlaysBlack;
6202       StartClocks();
6203       SetGameInfo();
6204       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6205       DisplayTitle(buf);
6206       if (first.sendName) {
6207         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6208         SendToProgram(buf, &first);
6209       }
6210       StartClocks();
6211     }
6212     ModeHighlight();
6213   }
6214
6215   /* Relay move to ICS or chess engine */
6216   if (appData.icsActive) {
6217     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6218         gameMode == IcsExamining) {
6219       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6220         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6221         SendToICS("draw ");
6222         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6223       }
6224       // also send plain move, in case ICS does not understand atomic claims
6225       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6226       ics_user_moved = 1;
6227     }
6228   } else {
6229     if (first.sendTime && (gameMode == BeginningOfGame ||
6230                            gameMode == MachinePlaysWhite ||
6231                            gameMode == MachinePlaysBlack)) {
6232       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6233     }
6234     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6235          // [HGM] book: if program might be playing, let it use book
6236         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6237         first.maybeThinking = TRUE;
6238     } else SendMoveToProgram(forwardMostMove-1, &first);
6239     if (currentMove == cmailOldMove + 1) {
6240       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6241     }
6242   }
6243
6244   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6245
6246   switch (gameMode) {
6247   case EditGame:
6248     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6249     case MT_NONE:
6250     case MT_CHECK:
6251       break;
6252     case MT_CHECKMATE:
6253     case MT_STAINMATE:
6254       if (WhiteOnMove(currentMove)) {
6255         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6256       } else {
6257         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6258       }
6259       break;
6260     case MT_STALEMATE:
6261       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6262       break;
6263     }
6264     break;
6265
6266   case MachinePlaysBlack:
6267   case MachinePlaysWhite:
6268     /* disable certain menu options while machine is thinking */
6269     SetMachineThinkingEnables();
6270     break;
6271
6272   default:
6273     break;
6274   }
6275
6276   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6277
6278   if(bookHit) { // [HGM] book: simulate book reply
6279         static char bookMove[MSG_SIZ]; // a bit generous?
6280
6281         programStats.nodes = programStats.depth = programStats.time =
6282         programStats.score = programStats.got_only_move = 0;
6283         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6284
6285         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6286         strcat(bookMove, bookHit);
6287         HandleMachineMove(bookMove, &first);
6288   }
6289   return 1;
6290 }
6291
6292 void
6293 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6294      Board board;
6295      int flags;
6296      ChessMove kind;
6297      int rf, ff, rt, ft;
6298      VOIDSTAR closure;
6299 {
6300     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6301     Markers *m = (Markers *) closure;
6302     if(rf == fromY && ff == fromX)
6303         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6304                          || kind == WhiteCapturesEnPassant
6305                          || kind == BlackCapturesEnPassant);
6306     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6307 }
6308
6309 void
6310 MarkTargetSquares(int clear)
6311 {
6312   int x, y;
6313   if(!appData.markers || !appData.highlightDragging ||
6314      !appData.testLegality || gameMode == EditPosition) return;
6315   if(clear) {
6316     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6317   } else {
6318     int capt = 0;
6319     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6320     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6321       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6322       if(capt)
6323       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6324     }
6325   }
6326   DrawPosition(TRUE, NULL);
6327 }
6328
6329 int
6330 Explode(Board board, int fromX, int fromY, int toX, int toY)
6331 {
6332     if(gameInfo.variant == VariantAtomic &&
6333        (board[toY][toX] != EmptySquare ||                     // capture?
6334         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6335                          board[fromY][fromX] == BlackPawn   )
6336       )) {
6337         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6338         return TRUE;
6339     }
6340     return FALSE;
6341 }
6342
6343 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6344
6345 void LeftClick(ClickType clickType, int xPix, int yPix)
6346 {
6347     int x, y;
6348     Boolean saveAnimate;
6349     static int second = 0, promotionChoice = 0, dragging = 0;
6350     char promoChoice = NULLCHAR;
6351
6352     if(appData.seekGraph && appData.icsActive && loggedOn &&
6353         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6354         SeekGraphClick(clickType, xPix, yPix, 0);
6355         return;
6356     }
6357
6358     if (clickType == Press) ErrorPopDown();
6359     MarkTargetSquares(1);
6360
6361     x = EventToSquare(xPix, BOARD_WIDTH);
6362     y = EventToSquare(yPix, BOARD_HEIGHT);
6363     if (!flipView && y >= 0) {
6364         y = BOARD_HEIGHT - 1 - y;
6365     }
6366     if (flipView && x >= 0) {
6367         x = BOARD_WIDTH - 1 - x;
6368     }
6369
6370     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6371         if(clickType == Release) return; // ignore upclick of click-click destination
6372         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6373         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6374         if(gameInfo.holdingsWidth &&
6375                 (WhiteOnMove(currentMove)
6376                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6377                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6378             // click in right holdings, for determining promotion piece
6379             ChessSquare p = boards[currentMove][y][x];
6380             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6381             if(p != EmptySquare) {
6382                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6383                 fromX = fromY = -1;
6384                 return;
6385             }
6386         }
6387         DrawPosition(FALSE, boards[currentMove]);
6388         return;
6389     }
6390
6391     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6392     if(clickType == Press
6393             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6394               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6395               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6396         return;
6397
6398     autoQueen = appData.alwaysPromoteToQueen;
6399
6400     if (fromX == -1) {
6401       gatingPiece = EmptySquare;
6402       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6403         if (clickType == Press) {
6404             /* First square */
6405             if (OKToStartUserMove(x, y)) {
6406                 fromX = x;
6407                 fromY = y;
6408                 second = 0;
6409                 MarkTargetSquares(0);
6410                 DragPieceBegin(xPix, yPix); dragging = 1;
6411                 if (appData.highlightDragging) {
6412                     SetHighlights(x, y, -1, -1);
6413                 }
6414             }
6415         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6416             DragPieceEnd(xPix, yPix); dragging = 0;
6417             DrawPosition(FALSE, NULL);
6418         }
6419         return;
6420       }
6421     }
6422
6423     /* fromX != -1 */
6424     if (clickType == Press && gameMode != EditPosition) {
6425         ChessSquare fromP;
6426         ChessSquare toP;
6427         int frc;
6428
6429         // ignore off-board to clicks
6430         if(y < 0 || x < 0) return;
6431
6432         /* Check if clicking again on the same color piece */
6433         fromP = boards[currentMove][fromY][fromX];
6434         toP = boards[currentMove][y][x];
6435         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6436         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6437              WhitePawn <= toP && toP <= WhiteKing &&
6438              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6439              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6440             (BlackPawn <= fromP && fromP <= BlackKing &&
6441              BlackPawn <= toP && toP <= BlackKing &&
6442              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6443              !(fromP == BlackKing && toP == BlackRook && frc))) {
6444             /* Clicked again on same color piece -- changed his mind */
6445             second = (x == fromX && y == fromY);
6446            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6447             if (appData.highlightDragging) {
6448                 SetHighlights(x, y, -1, -1);
6449             } else {
6450                 ClearHighlights();
6451             }
6452             if (OKToStartUserMove(x, y)) {
6453                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6454                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6455                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6456                  gatingPiece = boards[currentMove][fromY][fromX];
6457                 else gatingPiece = EmptySquare;
6458                 fromX = x;
6459                 fromY = y; dragging = 1;
6460                 MarkTargetSquares(0);
6461                 DragPieceBegin(xPix, yPix);
6462             }
6463            }
6464            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6465            second = FALSE; 
6466         }
6467         // ignore clicks on holdings
6468         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6469     }
6470
6471     if (clickType == Release && x == fromX && y == fromY) {
6472         DragPieceEnd(xPix, yPix); dragging = 0;
6473         if (appData.animateDragging) {
6474             /* Undo animation damage if any */
6475             DrawPosition(FALSE, NULL);
6476         }
6477         if (second) {
6478             /* Second up/down in same square; just abort move */
6479             second = 0;
6480             fromX = fromY = -1;
6481             gatingPiece = EmptySquare;
6482             ClearHighlights();
6483             gotPremove = 0;
6484             ClearPremoveHighlights();
6485         } else {
6486             /* First upclick in same square; start click-click mode */
6487             SetHighlights(x, y, -1, -1);
6488         }
6489         return;
6490     }
6491
6492     /* we now have a different from- and (possibly off-board) to-square */
6493     /* Completed move */
6494     toX = x;
6495     toY = y;
6496     saveAnimate = appData.animate;
6497     if (clickType == Press) {
6498         /* Finish clickclick move */
6499         if (appData.animate || appData.highlightLastMove) {
6500             SetHighlights(fromX, fromY, toX, toY);
6501         } else {
6502             ClearHighlights();
6503         }
6504     } else {
6505         /* Finish drag move */
6506         if (appData.highlightLastMove) {
6507             SetHighlights(fromX, fromY, toX, toY);
6508         } else {
6509             ClearHighlights();
6510         }
6511         DragPieceEnd(xPix, yPix); dragging = 0;
6512         /* Don't animate move and drag both */
6513         appData.animate = FALSE;
6514     }
6515
6516     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6517     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6518         ChessSquare piece = boards[currentMove][fromY][fromX];
6519         if(gameMode == EditPosition && piece != EmptySquare &&
6520            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6521             int n;
6522
6523             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6524                 n = PieceToNumber(piece - (int)BlackPawn);
6525                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6526                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6527                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6528             } else
6529             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6530                 n = PieceToNumber(piece);
6531                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6532                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6533                 boards[currentMove][n][BOARD_WIDTH-2]++;
6534             }
6535             boards[currentMove][fromY][fromX] = EmptySquare;
6536         }
6537         ClearHighlights();
6538         fromX = fromY = -1;
6539         DrawPosition(TRUE, boards[currentMove]);
6540         return;
6541     }
6542
6543     // off-board moves should not be highlighted
6544     if(x < 0 || y < 0) ClearHighlights();
6545
6546     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6547
6548     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6549         SetHighlights(fromX, fromY, toX, toY);
6550         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6551             // [HGM] super: promotion to captured piece selected from holdings
6552             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6553             promotionChoice = TRUE;
6554             // kludge follows to temporarily execute move on display, without promoting yet
6555             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6556             boards[currentMove][toY][toX] = p;
6557             DrawPosition(FALSE, boards[currentMove]);
6558             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6559             boards[currentMove][toY][toX] = q;
6560             DisplayMessage("Click in holdings to choose piece", "");
6561             return;
6562         }
6563         PromotionPopUp();
6564     } else {
6565         int oldMove = currentMove;
6566         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6567         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6568         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6569         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6570            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6571             DrawPosition(TRUE, boards[currentMove]);
6572         fromX = fromY = -1;
6573     }
6574     appData.animate = saveAnimate;
6575     if (appData.animate || appData.animateDragging) {
6576         /* Undo animation damage if needed */
6577         DrawPosition(FALSE, NULL);
6578     }
6579 }
6580
6581 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6582 {   // front-end-free part taken out of PieceMenuPopup
6583     int whichMenu; int xSqr, ySqr;
6584
6585     if(seekGraphUp) { // [HGM] seekgraph
6586         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6587         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6588         return -2;
6589     }
6590
6591     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6592          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6593         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6594         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6595         if(action == Press)   {
6596             originalFlip = flipView;
6597             flipView = !flipView; // temporarily flip board to see game from partners perspective
6598             DrawPosition(TRUE, partnerBoard);
6599             DisplayMessage(partnerStatus, "");
6600             partnerUp = TRUE;
6601         } else if(action == Release) {
6602             flipView = originalFlip;
6603             DrawPosition(TRUE, boards[currentMove]);
6604             partnerUp = FALSE;
6605         }
6606         return -2;
6607     }
6608
6609     xSqr = EventToSquare(x, BOARD_WIDTH);
6610     ySqr = EventToSquare(y, BOARD_HEIGHT);
6611     if (action == Release) UnLoadPV(); // [HGM] pv
6612     if (action != Press) return -2; // return code to be ignored
6613     switch (gameMode) {
6614       case IcsExamining:
6615         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6616       case EditPosition:
6617         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6618         if (xSqr < 0 || ySqr < 0) return -1;\r
6619         whichMenu = 0; // edit-position menu
6620         break;
6621       case IcsObserving:
6622         if(!appData.icsEngineAnalyze) return -1;
6623       case IcsPlayingWhite:
6624       case IcsPlayingBlack:
6625         if(!appData.zippyPlay) goto noZip;
6626       case AnalyzeMode:
6627       case AnalyzeFile:
6628       case MachinePlaysWhite:
6629       case MachinePlaysBlack:
6630       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6631         if (!appData.dropMenu) {
6632           LoadPV(x, y);
6633           return 2; // flag front-end to grab mouse events
6634         }
6635         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6636            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6637       case EditGame:
6638       noZip:
6639         if (xSqr < 0 || ySqr < 0) return -1;
6640         if (!appData.dropMenu || appData.testLegality &&
6641             gameInfo.variant != VariantBughouse &&
6642             gameInfo.variant != VariantCrazyhouse) return -1;
6643         whichMenu = 1; // drop menu
6644         break;
6645       default:
6646         return -1;
6647     }
6648
6649     if (((*fromX = xSqr) < 0) ||
6650         ((*fromY = ySqr) < 0)) {
6651         *fromX = *fromY = -1;
6652         return -1;
6653     }
6654     if (flipView)
6655       *fromX = BOARD_WIDTH - 1 - *fromX;
6656     else
6657       *fromY = BOARD_HEIGHT - 1 - *fromY;
6658
6659     return whichMenu;
6660 }
6661
6662 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6663 {
6664 //    char * hint = lastHint;
6665     FrontEndProgramStats stats;
6666
6667     stats.which = cps == &first ? 0 : 1;
6668     stats.depth = cpstats->depth;
6669     stats.nodes = cpstats->nodes;
6670     stats.score = cpstats->score;
6671     stats.time = cpstats->time;
6672     stats.pv = cpstats->movelist;
6673     stats.hint = lastHint;
6674     stats.an_move_index = 0;
6675     stats.an_move_count = 0;
6676
6677     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6678         stats.hint = cpstats->move_name;
6679         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6680         stats.an_move_count = cpstats->nr_moves;
6681     }
6682
6683     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
6684
6685     SetProgramStats( &stats );
6686 }
6687
6688 void
6689 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6690 {       // count all piece types
6691         int p, f, r;
6692         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6693         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6694         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6695                 p = board[r][f];
6696                 pCnt[p]++;
6697                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6698                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6699                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6700                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6701                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6702                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6703         }
6704 }
6705
6706 int
6707 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6708 {
6709         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6710         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6711
6712         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6713         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6714         if(myPawns == 2 && nMine == 3) // KPP
6715             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6716         if(myPawns == 1 && nMine == 2) // KP
6717             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6718         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6719             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6720         if(myPawns) return FALSE;
6721         if(pCnt[WhiteRook+side])
6722             return pCnt[BlackRook-side] ||
6723                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6724                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6725                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6726         if(pCnt[WhiteCannon+side]) {
6727             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6728             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6729         }
6730         if(pCnt[WhiteKnight+side])
6731             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6732         return FALSE;
6733 }
6734
6735 int
6736 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6737 {
6738         VariantClass v = gameInfo.variant;
6739
6740         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6741         if(v == VariantShatranj) return TRUE; // always winnable through baring
6742         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6743         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6744
6745         if(v == VariantXiangqi) {
6746                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6747
6748                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6749                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6750                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6751                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6752                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6753                 if(stale) // we have at least one last-rank P plus perhaps C
6754                     return majors // KPKX
6755                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6756                 else // KCA*E*
6757                     return pCnt[WhiteFerz+side] // KCAK
6758                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6759                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6760                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6761
6762         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6763                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6764
6765                 if(nMine == 1) return FALSE; // bare King
6766                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6767                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6768                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6769                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6770                 if(pCnt[WhiteKnight+side])
6771                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6772                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6773                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6774                 if(nBishops)
6775                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6776                 if(pCnt[WhiteAlfil+side])
6777                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6778                 if(pCnt[WhiteWazir+side])
6779                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6780         }
6781
6782         return TRUE;
6783 }
6784
6785 int
6786 Adjudicate(ChessProgramState *cps)
6787 {       // [HGM] some adjudications useful with buggy engines
6788         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6789         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6790         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6791         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6792         int k, count = 0; static int bare = 1;
6793         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6794         Boolean canAdjudicate = !appData.icsActive;
6795
6796         // most tests only when we understand the game, i.e. legality-checking on
6797             if( appData.testLegality )
6798             {   /* [HGM] Some more adjudications for obstinate engines */
6799                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6800                 static int moveCount = 6;
6801                 ChessMove result;
6802                 char *reason = NULL;
6803
6804                 /* Count what is on board. */
6805                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6806
6807                 /* Some material-based adjudications that have to be made before stalemate test */
6808                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6809                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6810                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6811                      if(canAdjudicate && appData.checkMates) {
6812                          if(engineOpponent)
6813                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6814                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6815                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6816                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6817                          return 1;
6818                      }
6819                 }
6820
6821                 /* Bare King in Shatranj (loses) or Losers (wins) */
6822                 if( nrW == 1 || nrB == 1) {
6823                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6824                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6825                      if(canAdjudicate && appData.checkMates) {
6826                          if(engineOpponent)
6827                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6828                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6829                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6830                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6831                          return 1;
6832                      }
6833                   } else
6834                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6835                   {    /* bare King */
6836                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6837                         if(canAdjudicate && appData.checkMates) {
6838                             /* but only adjudicate if adjudication enabled */
6839                             if(engineOpponent)
6840                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6841                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6842                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6843                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6844                             return 1;
6845                         }
6846                   }
6847                 } else bare = 1;
6848
6849
6850             // don't wait for engine to announce game end if we can judge ourselves
6851             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6852               case MT_CHECK:
6853                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6854                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6855                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6856                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6857                             checkCnt++;
6858                         if(checkCnt >= 2) {
6859                             reason = "Xboard adjudication: 3rd check";
6860                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6861                             break;
6862                         }
6863                     }
6864                 }
6865               case MT_NONE:
6866               default:
6867                 break;
6868               case MT_STALEMATE:
6869               case MT_STAINMATE:
6870                 reason = "Xboard adjudication: Stalemate";
6871                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6872                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6873                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6874                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6875                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6876                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6877                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6878                                                                         EP_CHECKMATE : EP_WINS);
6879                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6880                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6881                 }
6882                 break;
6883               case MT_CHECKMATE:
6884                 reason = "Xboard adjudication: Checkmate";
6885                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6886                 break;
6887             }
6888
6889                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6890                     case EP_STALEMATE:
6891                         result = GameIsDrawn; break;
6892                     case EP_CHECKMATE:
6893                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6894                     case EP_WINS:
6895                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6896                     default:
6897                         result = EndOfFile;
6898                 }
6899                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6900                     if(engineOpponent)
6901                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6902                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6903                     GameEnds( result, reason, GE_XBOARD );
6904                     return 1;
6905                 }
6906
6907                 /* Next absolutely insufficient mating material. */
6908                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6909                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6910                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6911
6912                      /* always flag draws, for judging claims */
6913                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6914
6915                      if(canAdjudicate && appData.materialDraws) {
6916                          /* but only adjudicate them if adjudication enabled */
6917                          if(engineOpponent) {
6918                            SendToProgram("force\n", engineOpponent); // suppress reply
6919                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6920                          }
6921                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6922                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6923                          return 1;
6924                      }
6925                 }
6926
6927                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6928                 if(gameInfo.variant == VariantXiangqi ?
6929                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6930                  : nrW + nrB == 4 &&
6931                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6932                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6933                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6934                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6935                    ) ) {
6936                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6937                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6938                           if(engineOpponent) {
6939                             SendToProgram("force\n", engineOpponent); // suppress reply
6940                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6941                           }
6942                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6943                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6944                           return 1;
6945                      }
6946                 } else moveCount = 6;
6947             }
6948         if (appData.debugMode) { int i;
6949             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6950                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6951                     appData.drawRepeats);
6952             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6953               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6954
6955         }
6956
6957         // Repetition draws and 50-move rule can be applied independently of legality testing
6958
6959                 /* Check for rep-draws */
6960                 count = 0;
6961                 for(k = forwardMostMove-2;
6962                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6963                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6964                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6965                     k-=2)
6966                 {   int rights=0;
6967                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6968                         /* compare castling rights */
6969                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6970                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6971                                 rights++; /* King lost rights, while rook still had them */
6972                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6973                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6974                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6975                                    rights++; /* but at least one rook lost them */
6976                         }
6977                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6978                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6979                                 rights++;
6980                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6981                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6982                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6983                                    rights++;
6984                         }
6985                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6986                             && appData.drawRepeats > 1) {
6987                              /* adjudicate after user-specified nr of repeats */
6988                              int result = GameIsDrawn;
6989                              char *details = "XBoard adjudication: repetition draw";
6990                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6991                                 // [HGM] xiangqi: check for forbidden perpetuals
6992                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6993                                 for(m=forwardMostMove; m>k; m-=2) {
6994                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6995                                         ourPerpetual = 0; // the current mover did not always check
6996                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6997                                         hisPerpetual = 0; // the opponent did not always check
6998                                 }
6999                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7000                                                                         ourPerpetual, hisPerpetual);
7001                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7002                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7003                                     details = "Xboard adjudication: perpetual checking";
7004                                 } else
7005                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7006                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7007                                 } else
7008                                 // Now check for perpetual chases
7009                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7010                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7011                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7012                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7013                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7014                                         details = "Xboard adjudication: perpetual chasing";
7015                                     } else
7016                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7017                                         break; // Abort repetition-checking loop.
7018                                 }
7019                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7020                              }
7021                              if(engineOpponent) {
7022                                SendToProgram("force\n", engineOpponent); // suppress reply
7023                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7024                              }
7025                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7026                              GameEnds( result, details, GE_XBOARD );
7027                              return 1;
7028                         }
7029                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7030                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7031                     }
7032                 }
7033
7034                 /* Now we test for 50-move draws. Determine ply count */
7035                 count = forwardMostMove;
7036                 /* look for last irreversble move */
7037                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7038                     count--;
7039                 /* if we hit starting position, add initial plies */
7040                 if( count == backwardMostMove )
7041                     count -= initialRulePlies;
7042                 count = forwardMostMove - count;
7043                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7044                         // adjust reversible move counter for checks in Xiangqi
7045                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7046                         if(i < backwardMostMove) i = backwardMostMove;
7047                         while(i <= forwardMostMove) {
7048                                 lastCheck = inCheck; // check evasion does not count
7049                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7050                                 if(inCheck || lastCheck) count--; // check does not count
7051                                 i++;
7052                         }
7053                 }
7054                 if( count >= 100)
7055                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7056                          /* this is used to judge if draw claims are legal */
7057                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7058                          if(engineOpponent) {
7059                            SendToProgram("force\n", engineOpponent); // suppress reply
7060                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7061                          }
7062                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7063                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7064                          return 1;
7065                 }
7066
7067                 /* if draw offer is pending, treat it as a draw claim
7068                  * when draw condition present, to allow engines a way to
7069                  * claim draws before making their move to avoid a race
7070                  * condition occurring after their move
7071                  */
7072                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7073                          char *p = NULL;
7074                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7075                              p = "Draw claim: 50-move rule";
7076                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7077                              p = "Draw claim: 3-fold repetition";
7078                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7079                              p = "Draw claim: insufficient mating material";
7080                          if( p != NULL && canAdjudicate) {
7081                              if(engineOpponent) {
7082                                SendToProgram("force\n", engineOpponent); // suppress reply
7083                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7084                              }
7085                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7086                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7087                              return 1;
7088                          }
7089                 }
7090
7091                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7092                     if(engineOpponent) {
7093                       SendToProgram("force\n", engineOpponent); // suppress reply
7094                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7095                     }
7096                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7097                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7098                     return 1;
7099                 }
7100         return 0;
7101 }
7102
7103 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7104 {   // [HGM] book: this routine intercepts moves to simulate book replies
7105     char *bookHit = NULL;
7106
7107     //first determine if the incoming move brings opponent into his book
7108     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7109         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7110     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7111     if(bookHit != NULL && !cps->bookSuspend) {
7112         // make sure opponent is not going to reply after receiving move to book position
7113         SendToProgram("force\n", cps);
7114         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7115     }
7116     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7117     // now arrange restart after book miss
7118     if(bookHit) {
7119         // after a book hit we never send 'go', and the code after the call to this routine
7120         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7121         char buf[MSG_SIZ];
7122         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7123         SendToProgram(buf, cps);
7124         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7125     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7126         SendToProgram("go\n", cps);
7127         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7128     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7129         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7130             SendToProgram("go\n", cps);
7131         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7132     }
7133     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7134 }
7135
7136 char *savedMessage;
7137 ChessProgramState *savedState;
7138 void DeferredBookMove(void)
7139 {
7140         if(savedState->lastPing != savedState->lastPong)
7141                     ScheduleDelayedEvent(DeferredBookMove, 10);
7142         else
7143         HandleMachineMove(savedMessage, savedState);
7144 }
7145
7146 void
7147 HandleMachineMove(message, cps)
7148      char *message;
7149      ChessProgramState *cps;
7150 {
7151     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7152     char realname[MSG_SIZ];
7153     int fromX, fromY, toX, toY;
7154     ChessMove moveType;
7155     char promoChar;
7156     char *p;
7157     int machineWhite;
7158     char *bookHit;
7159
7160     cps->userError = 0;
7161
7162 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7163     /*
7164      * Kludge to ignore BEL characters
7165      */
7166     while (*message == '\007') message++;
7167
7168     /*
7169      * [HGM] engine debug message: ignore lines starting with '#' character
7170      */
7171     if(cps->debug && *message == '#') return;
7172
7173     /*
7174      * Look for book output
7175      */
7176     if (cps == &first && bookRequested) {
7177         if (message[0] == '\t' || message[0] == ' ') {
7178             /* Part of the book output is here; append it */
7179             strcat(bookOutput, message);
7180             strcat(bookOutput, "  \n");
7181             return;
7182         } else if (bookOutput[0] != NULLCHAR) {
7183             /* All of book output has arrived; display it */
7184             char *p = bookOutput;
7185             while (*p != NULLCHAR) {
7186                 if (*p == '\t') *p = ' ';
7187                 p++;
7188             }
7189             DisplayInformation(bookOutput);
7190             bookRequested = FALSE;
7191             /* Fall through to parse the current output */
7192         }
7193     }
7194
7195     /*
7196      * Look for machine move.
7197      */
7198     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7199         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7200     {
7201         /* This method is only useful on engines that support ping */
7202         if (cps->lastPing != cps->lastPong) {
7203           if (gameMode == BeginningOfGame) {
7204             /* Extra move from before last new; ignore */
7205             if (appData.debugMode) {
7206                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7207             }
7208           } else {
7209             if (appData.debugMode) {
7210                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7211                         cps->which, gameMode);
7212             }
7213
7214             SendToProgram("undo\n", cps);
7215           }
7216           return;
7217         }
7218
7219         switch (gameMode) {
7220           case BeginningOfGame:
7221             /* Extra move from before last reset; ignore */
7222             if (appData.debugMode) {
7223                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7224             }
7225             return;
7226
7227           case EndOfGame:
7228           case IcsIdle:
7229           default:
7230             /* Extra move after we tried to stop.  The mode test is
7231                not a reliable way of detecting this problem, but it's
7232                the best we can do on engines that don't support ping.
7233             */
7234             if (appData.debugMode) {
7235                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7236                         cps->which, gameMode);
7237             }
7238             SendToProgram("undo\n", cps);
7239             return;
7240
7241           case MachinePlaysWhite:
7242           case IcsPlayingWhite:
7243             machineWhite = TRUE;
7244             break;
7245
7246           case MachinePlaysBlack:
7247           case IcsPlayingBlack:
7248             machineWhite = FALSE;
7249             break;
7250
7251           case TwoMachinesPlay:
7252             machineWhite = (cps->twoMachinesColor[0] == 'w');
7253             break;
7254         }
7255         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7256             if (appData.debugMode) {
7257                 fprintf(debugFP,
7258                         "Ignoring move out of turn by %s, gameMode %d"
7259                         ", forwardMost %d\n",
7260                         cps->which, gameMode, forwardMostMove);
7261             }
7262             return;
7263         }
7264
7265     if (appData.debugMode) { int f = forwardMostMove;
7266         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7267                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7268                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7269     }
7270         if(cps->alphaRank) AlphaRank(machineMove, 4);
7271         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7272                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7273             /* Machine move could not be parsed; ignore it. */
7274           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7275                     machineMove, cps->which);
7276             DisplayError(buf1, 0);
7277             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7278                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7279             if (gameMode == TwoMachinesPlay) {
7280               GameEnds(machineWhite ? BlackWins : WhiteWins,
7281                        buf1, GE_XBOARD);
7282             }
7283             return;
7284         }
7285
7286         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7287         /* So we have to redo legality test with true e.p. status here,  */
7288         /* to make sure an illegal e.p. capture does not slip through,   */
7289         /* to cause a forfeit on a justified illegal-move complaint      */
7290         /* of the opponent.                                              */
7291         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7292            ChessMove moveType;
7293            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7294                              fromY, fromX, toY, toX, promoChar);
7295             if (appData.debugMode) {
7296                 int i;
7297                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7298                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7299                 fprintf(debugFP, "castling rights\n");
7300             }
7301             if(moveType == IllegalMove) {
7302               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7303                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7304                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7305                            buf1, GE_XBOARD);
7306                 return;
7307            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7308            /* [HGM] Kludge to handle engines that send FRC-style castling
7309               when they shouldn't (like TSCP-Gothic) */
7310            switch(moveType) {
7311              case WhiteASideCastleFR:
7312              case BlackASideCastleFR:
7313                toX+=2;
7314                currentMoveString[2]++;
7315                break;
7316              case WhiteHSideCastleFR:
7317              case BlackHSideCastleFR:
7318                toX--;
7319                currentMoveString[2]--;
7320                break;
7321              default: ; // nothing to do, but suppresses warning of pedantic compilers
7322            }
7323         }
7324         hintRequested = FALSE;
7325         lastHint[0] = NULLCHAR;
7326         bookRequested = FALSE;
7327         /* Program may be pondering now */
7328         cps->maybeThinking = TRUE;
7329         if (cps->sendTime == 2) cps->sendTime = 1;
7330         if (cps->offeredDraw) cps->offeredDraw--;
7331
7332         /* currentMoveString is set as a side-effect of ParseOneMove */
7333         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7334         strcat(machineMove, "\n");
7335         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7336
7337         /* [AS] Save move info*/
7338         pvInfoList[ forwardMostMove ].score = programStats.score;
7339         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7340         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7341
7342         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7343
7344         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7345         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7346             int count = 0;
7347
7348             while( count < adjudicateLossPlies ) {
7349                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7350
7351                 if( count & 1 ) {
7352                     score = -score; /* Flip score for winning side */
7353                 }
7354
7355                 if( score > adjudicateLossThreshold ) {
7356                     break;
7357                 }
7358
7359                 count++;
7360             }
7361
7362             if( count >= adjudicateLossPlies ) {
7363                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7364
7365                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7366                     "Xboard adjudication",
7367                     GE_XBOARD );
7368
7369                 return;
7370             }
7371         }
7372
7373         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7374
7375 #if ZIPPY
7376         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7377             first.initDone) {
7378           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7379                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7380                 SendToICS("draw ");
7381                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7382           }
7383           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7384           ics_user_moved = 1;
7385           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7386                 char buf[3*MSG_SIZ];
7387
7388                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7389                         programStats.score / 100.,
7390                         programStats.depth,
7391                         programStats.time / 100.,
7392                         (unsigned int)programStats.nodes,
7393                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7394                         programStats.movelist);
7395                 SendToICS(buf);
7396 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7397           }
7398         }
7399 #endif
7400
7401         /* [AS] Clear stats for next move */
7402         ClearProgramStats();
7403         thinkOutput[0] = NULLCHAR;
7404         hiddenThinkOutputState = 0;
7405
7406         bookHit = NULL;
7407         if (gameMode == TwoMachinesPlay) {
7408             /* [HGM] relaying draw offers moved to after reception of move */
7409             /* and interpreting offer as claim if it brings draw condition */
7410             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7411                 SendToProgram("draw\n", cps->other);
7412             }
7413             if (cps->other->sendTime) {
7414                 SendTimeRemaining(cps->other,
7415                                   cps->other->twoMachinesColor[0] == 'w');
7416             }
7417             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7418             if (firstMove && !bookHit) {
7419                 firstMove = FALSE;
7420                 if (cps->other->useColors) {
7421                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7422                 }
7423                 SendToProgram("go\n", cps->other);
7424             }
7425             cps->other->maybeThinking = TRUE;
7426         }
7427
7428         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7429
7430         if (!pausing && appData.ringBellAfterMoves) {
7431             RingBell();
7432         }
7433
7434         /*
7435          * Reenable menu items that were disabled while
7436          * machine was thinking
7437          */
7438         if (gameMode != TwoMachinesPlay)
7439             SetUserThinkingEnables();
7440
7441         // [HGM] book: after book hit opponent has received move and is now in force mode
7442         // force the book reply into it, and then fake that it outputted this move by jumping
7443         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7444         if(bookHit) {
7445                 static char bookMove[MSG_SIZ]; // a bit generous?
7446
7447                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7448                 strcat(bookMove, bookHit);
7449                 message = bookMove;
7450                 cps = cps->other;
7451                 programStats.nodes = programStats.depth = programStats.time =
7452                 programStats.score = programStats.got_only_move = 0;
7453                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7454
7455                 if(cps->lastPing != cps->lastPong) {
7456                     savedMessage = message; // args for deferred call
7457                     savedState = cps;
7458                     ScheduleDelayedEvent(DeferredBookMove, 10);
7459                     return;
7460                 }
7461                 goto FakeBookMove;
7462         }
7463
7464         return;
7465     }
7466
7467     /* Set special modes for chess engines.  Later something general
7468      *  could be added here; for now there is just one kludge feature,
7469      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7470      *  when "xboard" is given as an interactive command.
7471      */
7472     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7473         cps->useSigint = FALSE;
7474         cps->useSigterm = FALSE;
7475     }
7476     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7477       ParseFeatures(message+8, cps);
7478       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7479     }
7480
7481     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7482       int dummy, s=6; char buf[MSG_SIZ];
7483       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7484       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7485       ParseFEN(boards[0], &dummy, message+s);
7486       DrawPosition(TRUE, boards[0]);
7487       startedFromSetupPosition = TRUE;
7488       return;
7489     }
7490     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7491      * want this, I was asked to put it in, and obliged.
7492      */
7493     if (!strncmp(message, "setboard ", 9)) {
7494         Board initial_position;
7495
7496         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7497
7498         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7499             DisplayError(_("Bad FEN received from engine"), 0);
7500             return ;
7501         } else {
7502            Reset(TRUE, FALSE);
7503            CopyBoard(boards[0], initial_position);
7504            initialRulePlies = FENrulePlies;
7505            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7506            else gameMode = MachinePlaysBlack;
7507            DrawPosition(FALSE, boards[currentMove]);
7508         }
7509         return;
7510     }
7511
7512     /*
7513      * Look for communication commands
7514      */
7515     if (!strncmp(message, "telluser ", 9)) {
7516         if(message[9] == '\\' && message[10] == '\\')
7517             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7518         DisplayNote(message + 9);
7519         return;
7520     }
7521     if (!strncmp(message, "tellusererror ", 14)) {
7522         cps->userError = 1;
7523         if(message[14] == '\\' && message[15] == '\\')
7524             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7525         DisplayError(message + 14, 0);
7526         return;
7527     }
7528     if (!strncmp(message, "tellopponent ", 13)) {
7529       if (appData.icsActive) {
7530         if (loggedOn) {
7531           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7532           SendToICS(buf1);
7533         }
7534       } else {
7535         DisplayNote(message + 13);
7536       }
7537       return;
7538     }
7539     if (!strncmp(message, "tellothers ", 11)) {
7540       if (appData.icsActive) {
7541         if (loggedOn) {
7542           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7543           SendToICS(buf1);
7544         }
7545       }
7546       return;
7547     }
7548     if (!strncmp(message, "tellall ", 8)) {
7549       if (appData.icsActive) {
7550         if (loggedOn) {
7551           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7552           SendToICS(buf1);
7553         }
7554       } else {
7555         DisplayNote(message + 8);
7556       }
7557       return;
7558     }
7559     if (strncmp(message, "warning", 7) == 0) {
7560         /* Undocumented feature, use tellusererror in new code */
7561         DisplayError(message, 0);
7562         return;
7563     }
7564     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7565         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7566         strcat(realname, " query");
7567         AskQuestion(realname, buf2, buf1, cps->pr);
7568         return;
7569     }
7570     /* Commands from the engine directly to ICS.  We don't allow these to be
7571      *  sent until we are logged on. Crafty kibitzes have been known to
7572      *  interfere with the login process.
7573      */
7574     if (loggedOn) {
7575         if (!strncmp(message, "tellics ", 8)) {
7576             SendToICS(message + 8);
7577             SendToICS("\n");
7578             return;
7579         }
7580         if (!strncmp(message, "tellicsnoalias ", 15)) {
7581             SendToICS(ics_prefix);
7582             SendToICS(message + 15);
7583             SendToICS("\n");
7584             return;
7585         }
7586         /* The following are for backward compatibility only */
7587         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7588             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7589             SendToICS(ics_prefix);
7590             SendToICS(message);
7591             SendToICS("\n");
7592             return;
7593         }
7594     }
7595     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7596         return;
7597     }
7598     /*
7599      * If the move is illegal, cancel it and redraw the board.
7600      * Also deal with other error cases.  Matching is rather loose
7601      * here to accommodate engines written before the spec.
7602      */
7603     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7604         strncmp(message, "Error", 5) == 0) {
7605         if (StrStr(message, "name") ||
7606             StrStr(message, "rating") || StrStr(message, "?") ||
7607             StrStr(message, "result") || StrStr(message, "board") ||
7608             StrStr(message, "bk") || StrStr(message, "computer") ||
7609             StrStr(message, "variant") || StrStr(message, "hint") ||
7610             StrStr(message, "random") || StrStr(message, "depth") ||
7611             StrStr(message, "accepted")) {
7612             return;
7613         }
7614         if (StrStr(message, "protover")) {
7615           /* Program is responding to input, so it's apparently done
7616              initializing, and this error message indicates it is
7617              protocol version 1.  So we don't need to wait any longer
7618              for it to initialize and send feature commands. */
7619           FeatureDone(cps, 1);
7620           cps->protocolVersion = 1;
7621           return;
7622         }
7623         cps->maybeThinking = FALSE;
7624
7625         if (StrStr(message, "draw")) {
7626             /* Program doesn't have "draw" command */
7627             cps->sendDrawOffers = 0;
7628             return;
7629         }
7630         if (cps->sendTime != 1 &&
7631             (StrStr(message, "time") || StrStr(message, "otim"))) {
7632           /* Program apparently doesn't have "time" or "otim" command */
7633           cps->sendTime = 0;
7634           return;
7635         }
7636         if (StrStr(message, "analyze")) {
7637             cps->analysisSupport = FALSE;
7638             cps->analyzing = FALSE;
7639             Reset(FALSE, TRUE);
7640             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7641             DisplayError(buf2, 0);
7642             return;
7643         }
7644         if (StrStr(message, "(no matching move)st")) {
7645           /* Special kludge for GNU Chess 4 only */
7646           cps->stKludge = TRUE;
7647           SendTimeControl(cps, movesPerSession, timeControl,
7648                           timeIncrement, appData.searchDepth,
7649                           searchTime);
7650           return;
7651         }
7652         if (StrStr(message, "(no matching move)sd")) {
7653           /* Special kludge for GNU Chess 4 only */
7654           cps->sdKludge = TRUE;
7655           SendTimeControl(cps, movesPerSession, timeControl,
7656                           timeIncrement, appData.searchDepth,
7657                           searchTime);
7658           return;
7659         }
7660         if (!StrStr(message, "llegal")) {
7661             return;
7662         }
7663         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7664             gameMode == IcsIdle) return;
7665         if (forwardMostMove <= backwardMostMove) return;
7666         if (pausing) PauseEvent();
7667       if(appData.forceIllegal) {
7668             // [HGM] illegal: machine refused move; force position after move into it
7669           SendToProgram("force\n", cps);
7670           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7671                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7672                 // when black is to move, while there might be nothing on a2 or black
7673                 // might already have the move. So send the board as if white has the move.
7674                 // But first we must change the stm of the engine, as it refused the last move
7675                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7676                 if(WhiteOnMove(forwardMostMove)) {
7677                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7678                     SendBoard(cps, forwardMostMove); // kludgeless board
7679                 } else {
7680                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7681                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7682                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7683                 }
7684           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7685             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7686                  gameMode == TwoMachinesPlay)
7687               SendToProgram("go\n", cps);
7688             return;
7689       } else
7690         if (gameMode == PlayFromGameFile) {
7691             /* Stop reading this game file */
7692             gameMode = EditGame;
7693             ModeHighlight();
7694         }
7695         currentMove = forwardMostMove-1;
7696         DisplayMove(currentMove-1); /* before DisplayMoveError */
7697         SwitchClocks(forwardMostMove-1); // [HGM] race
7698         DisplayBothClocks();
7699         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7700                 parseList[currentMove], cps->which);
7701         DisplayMoveError(buf1);
7702         DrawPosition(FALSE, boards[currentMove]);
7703
7704         /* [HGM] illegal-move claim should forfeit game when Xboard */
7705         /* only passes fully legal moves                            */
7706         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7707             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7708                                 "False illegal-move claim", GE_XBOARD );
7709         }
7710         return;
7711     }
7712     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7713         /* Program has a broken "time" command that
7714            outputs a string not ending in newline.
7715            Don't use it. */
7716         cps->sendTime = 0;
7717     }
7718
7719     /*
7720      * If chess program startup fails, exit with an error message.
7721      * Attempts to recover here are futile.
7722      */
7723     if ((StrStr(message, "unknown host") != NULL)
7724         || (StrStr(message, "No remote directory") != NULL)
7725         || (StrStr(message, "not found") != NULL)
7726         || (StrStr(message, "No such file") != NULL)
7727         || (StrStr(message, "can't alloc") != NULL)
7728         || (StrStr(message, "Permission denied") != NULL)) {
7729
7730         cps->maybeThinking = FALSE;
7731         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7732                 cps->which, cps->program, cps->host, message);
7733         RemoveInputSource(cps->isr);
7734         DisplayFatalError(buf1, 0, 1);
7735         return;
7736     }
7737
7738     /*
7739      * Look for hint output
7740      */
7741     if (sscanf(message, "Hint: %s", buf1) == 1) {
7742         if (cps == &first && hintRequested) {
7743             hintRequested = FALSE;
7744             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7745                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7746                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7747                                     PosFlags(forwardMostMove),
7748                                     fromY, fromX, toY, toX, promoChar, buf1);
7749                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7750                 DisplayInformation(buf2);
7751             } else {
7752                 /* Hint move could not be parsed!? */
7753               snprintf(buf2, sizeof(buf2),
7754                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7755                         buf1, cps->which);
7756                 DisplayError(buf2, 0);
7757             }
7758         } else {
7759           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7760         }
7761         return;
7762     }
7763
7764     /*
7765      * Ignore other messages if game is not in progress
7766      */
7767     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7768         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7769
7770     /*
7771      * look for win, lose, draw, or draw offer
7772      */
7773     if (strncmp(message, "1-0", 3) == 0) {
7774         char *p, *q, *r = "";
7775         p = strchr(message, '{');
7776         if (p) {
7777             q = strchr(p, '}');
7778             if (q) {
7779                 *q = NULLCHAR;
7780                 r = p + 1;
7781             }
7782         }
7783         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7784         return;
7785     } else if (strncmp(message, "0-1", 3) == 0) {
7786         char *p, *q, *r = "";
7787         p = strchr(message, '{');
7788         if (p) {
7789             q = strchr(p, '}');
7790             if (q) {
7791                 *q = NULLCHAR;
7792                 r = p + 1;
7793             }
7794         }
7795         /* Kludge for Arasan 4.1 bug */
7796         if (strcmp(r, "Black resigns") == 0) {
7797             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7798             return;
7799         }
7800         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7801         return;
7802     } else if (strncmp(message, "1/2", 3) == 0) {
7803         char *p, *q, *r = "";
7804         p = strchr(message, '{');
7805         if (p) {
7806             q = strchr(p, '}');
7807             if (q) {
7808                 *q = NULLCHAR;
7809                 r = p + 1;
7810             }
7811         }
7812
7813         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7814         return;
7815
7816     } else if (strncmp(message, "White resign", 12) == 0) {
7817         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7818         return;
7819     } else if (strncmp(message, "Black resign", 12) == 0) {
7820         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7821         return;
7822     } else if (strncmp(message, "White matches", 13) == 0 ||
7823                strncmp(message, "Black matches", 13) == 0   ) {
7824         /* [HGM] ignore GNUShogi noises */
7825         return;
7826     } else if (strncmp(message, "White", 5) == 0 &&
7827                message[5] != '(' &&
7828                StrStr(message, "Black") == NULL) {
7829         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7830         return;
7831     } else if (strncmp(message, "Black", 5) == 0 &&
7832                message[5] != '(') {
7833         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7834         return;
7835     } else if (strcmp(message, "resign") == 0 ||
7836                strcmp(message, "computer resigns") == 0) {
7837         switch (gameMode) {
7838           case MachinePlaysBlack:
7839           case IcsPlayingBlack:
7840             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7841             break;
7842           case MachinePlaysWhite:
7843           case IcsPlayingWhite:
7844             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7845             break;
7846           case TwoMachinesPlay:
7847             if (cps->twoMachinesColor[0] == 'w')
7848               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7849             else
7850               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7851             break;
7852           default:
7853             /* can't happen */
7854             break;
7855         }
7856         return;
7857     } else if (strncmp(message, "opponent mates", 14) == 0) {
7858         switch (gameMode) {
7859           case MachinePlaysBlack:
7860           case IcsPlayingBlack:
7861             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7862             break;
7863           case MachinePlaysWhite:
7864           case IcsPlayingWhite:
7865             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7866             break;
7867           case TwoMachinesPlay:
7868             if (cps->twoMachinesColor[0] == 'w')
7869               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7870             else
7871               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7872             break;
7873           default:
7874             /* can't happen */
7875             break;
7876         }
7877         return;
7878     } else if (strncmp(message, "computer mates", 14) == 0) {
7879         switch (gameMode) {
7880           case MachinePlaysBlack:
7881           case IcsPlayingBlack:
7882             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7883             break;
7884           case MachinePlaysWhite:
7885           case IcsPlayingWhite:
7886             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7887             break;
7888           case TwoMachinesPlay:
7889             if (cps->twoMachinesColor[0] == 'w')
7890               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7891             else
7892               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7893             break;
7894           default:
7895             /* can't happen */
7896             break;
7897         }
7898         return;
7899     } else if (strncmp(message, "checkmate", 9) == 0) {
7900         if (WhiteOnMove(forwardMostMove)) {
7901             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7902         } else {
7903             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7904         }
7905         return;
7906     } else if (strstr(message, "Draw") != NULL ||
7907                strstr(message, "game is a draw") != NULL) {
7908         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7909         return;
7910     } else if (strstr(message, "offer") != NULL &&
7911                strstr(message, "draw") != NULL) {
7912 #if ZIPPY
7913         if (appData.zippyPlay && first.initDone) {
7914             /* Relay offer to ICS */
7915             SendToICS(ics_prefix);
7916             SendToICS("draw\n");
7917         }
7918 #endif
7919         cps->offeredDraw = 2; /* valid until this engine moves twice */
7920         if (gameMode == TwoMachinesPlay) {
7921             if (cps->other->offeredDraw) {
7922                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7923             /* [HGM] in two-machine mode we delay relaying draw offer      */
7924             /* until after we also have move, to see if it is really claim */
7925             }
7926         } else if (gameMode == MachinePlaysWhite ||
7927                    gameMode == MachinePlaysBlack) {
7928           if (userOfferedDraw) {
7929             DisplayInformation(_("Machine accepts your draw offer"));
7930             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7931           } else {
7932             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7933           }
7934         }
7935     }
7936
7937
7938     /*
7939      * Look for thinking output
7940      */
7941     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7942           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7943                                 ) {
7944         int plylev, mvleft, mvtot, curscore, time;
7945         char mvname[MOVE_LEN];
7946         u64 nodes; // [DM]
7947         char plyext;
7948         int ignore = FALSE;
7949         int prefixHint = FALSE;
7950         mvname[0] = NULLCHAR;
7951
7952         switch (gameMode) {
7953           case MachinePlaysBlack:
7954           case IcsPlayingBlack:
7955             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7956             break;
7957           case MachinePlaysWhite:
7958           case IcsPlayingWhite:
7959             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7960             break;
7961           case AnalyzeMode:
7962           case AnalyzeFile:
7963             break;
7964           case IcsObserving: /* [DM] icsEngineAnalyze */
7965             if (!appData.icsEngineAnalyze) ignore = TRUE;
7966             break;
7967           case TwoMachinesPlay:
7968             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7969                 ignore = TRUE;
7970             }
7971             break;
7972           default:
7973             ignore = TRUE;
7974             break;
7975         }
7976
7977         if (!ignore) {
7978             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7979             buf1[0] = NULLCHAR;
7980             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7981                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7982
7983                 if (plyext != ' ' && plyext != '\t') {
7984                     time *= 100;
7985                 }
7986
7987                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7988                 if( cps->scoreIsAbsolute &&
7989                     ( gameMode == MachinePlaysBlack ||
7990                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7991                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7992                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7993                      !WhiteOnMove(currentMove)
7994                     ) )
7995                 {
7996                     curscore = -curscore;
7997                 }
7998
7999
8000                 tempStats.depth = plylev;
8001                 tempStats.nodes = nodes;
8002                 tempStats.time = time;
8003                 tempStats.score = curscore;
8004                 tempStats.got_only_move = 0;
8005
8006                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8007                         int ticklen;
8008
8009                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8010                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8011                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8012                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8013                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8014                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8015                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8016                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8017                 }
8018
8019                 /* Buffer overflow protection */
8020                 if (buf1[0] != NULLCHAR) {
8021                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8022                         && appData.debugMode) {
8023                         fprintf(debugFP,
8024                                 "PV is too long; using the first %u bytes.\n",
8025                                 (unsigned) sizeof(tempStats.movelist) - 1);
8026                     }
8027
8028                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8029                 } else {
8030                     sprintf(tempStats.movelist, " no PV\n");
8031                 }
8032
8033                 if (tempStats.seen_stat) {
8034                     tempStats.ok_to_send = 1;
8035                 }
8036
8037                 if (strchr(tempStats.movelist, '(') != NULL) {
8038                     tempStats.line_is_book = 1;
8039                     tempStats.nr_moves = 0;
8040                     tempStats.moves_left = 0;
8041                 } else {
8042                     tempStats.line_is_book = 0;
8043                 }
8044
8045                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8046                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8047
8048                 SendProgramStatsToFrontend( cps, &tempStats );
8049
8050                 /*
8051                     [AS] Protect the thinkOutput buffer from overflow... this
8052                     is only useful if buf1 hasn't overflowed first!
8053                 */
8054                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8055                          plylev,
8056                          (gameMode == TwoMachinesPlay ?
8057                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8058                          ((double) curscore) / 100.0,
8059                          prefixHint ? lastHint : "",
8060                          prefixHint ? " " : "" );
8061
8062                 if( buf1[0] != NULLCHAR ) {
8063                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8064
8065                     if( strlen(buf1) > max_len ) {
8066                         if( appData.debugMode) {
8067                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8068                         }
8069                         buf1[max_len+1] = '\0';
8070                     }
8071
8072                     strcat( thinkOutput, buf1 );
8073                 }
8074
8075                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8076                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8077                     DisplayMove(currentMove - 1);
8078                 }
8079                 return;
8080
8081             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8082                 /* crafty (9.25+) says "(only move) <move>"
8083                  * if there is only 1 legal move
8084                  */
8085                 sscanf(p, "(only move) %s", buf1);
8086                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8087                 sprintf(programStats.movelist, "%s (only move)", buf1);
8088                 programStats.depth = 1;
8089                 programStats.nr_moves = 1;
8090                 programStats.moves_left = 1;
8091                 programStats.nodes = 1;
8092                 programStats.time = 1;
8093                 programStats.got_only_move = 1;
8094
8095                 /* Not really, but we also use this member to
8096                    mean "line isn't going to change" (Crafty
8097                    isn't searching, so stats won't change) */
8098                 programStats.line_is_book = 1;
8099
8100                 SendProgramStatsToFrontend( cps, &programStats );
8101
8102                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8103                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8104                     DisplayMove(currentMove - 1);
8105                 }
8106                 return;
8107             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8108                               &time, &nodes, &plylev, &mvleft,
8109                               &mvtot, mvname) >= 5) {
8110                 /* The stat01: line is from Crafty (9.29+) in response
8111                    to the "." command */
8112                 programStats.seen_stat = 1;
8113                 cps->maybeThinking = TRUE;
8114
8115                 if (programStats.got_only_move || !appData.periodicUpdates)
8116                   return;
8117
8118                 programStats.depth = plylev;
8119                 programStats.time = time;
8120                 programStats.nodes = nodes;
8121                 programStats.moves_left = mvleft;
8122                 programStats.nr_moves = mvtot;
8123                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8124                 programStats.ok_to_send = 1;
8125                 programStats.movelist[0] = '\0';
8126
8127                 SendProgramStatsToFrontend( cps, &programStats );
8128
8129                 return;
8130
8131             } else if (strncmp(message,"++",2) == 0) {
8132                 /* Crafty 9.29+ outputs this */
8133                 programStats.got_fail = 2;
8134                 return;
8135
8136             } else if (strncmp(message,"--",2) == 0) {
8137                 /* Crafty 9.29+ outputs this */
8138                 programStats.got_fail = 1;
8139                 return;
8140
8141             } else if (thinkOutput[0] != NULLCHAR &&
8142                        strncmp(message, "    ", 4) == 0) {
8143                 unsigned message_len;
8144
8145                 p = message;
8146                 while (*p && *p == ' ') p++;
8147
8148                 message_len = strlen( p );
8149
8150                 /* [AS] Avoid buffer overflow */
8151                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8152                     strcat(thinkOutput, " ");
8153                     strcat(thinkOutput, p);
8154                 }
8155
8156                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8157                     strcat(programStats.movelist, " ");
8158                     strcat(programStats.movelist, p);
8159                 }
8160
8161                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8162                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8163                     DisplayMove(currentMove - 1);
8164                 }
8165                 return;
8166             }
8167         }
8168         else {
8169             buf1[0] = NULLCHAR;
8170
8171             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8172                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8173             {
8174                 ChessProgramStats cpstats;
8175
8176                 if (plyext != ' ' && plyext != '\t') {
8177                     time *= 100;
8178                 }
8179
8180                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8181                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8182                     curscore = -curscore;
8183                 }
8184
8185                 cpstats.depth = plylev;
8186                 cpstats.nodes = nodes;
8187                 cpstats.time = time;
8188                 cpstats.score = curscore;
8189                 cpstats.got_only_move = 0;
8190                 cpstats.movelist[0] = '\0';
8191
8192                 if (buf1[0] != NULLCHAR) {
8193                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8194                 }
8195
8196                 cpstats.ok_to_send = 0;
8197                 cpstats.line_is_book = 0;
8198                 cpstats.nr_moves = 0;
8199                 cpstats.moves_left = 0;
8200
8201                 SendProgramStatsToFrontend( cps, &cpstats );
8202             }
8203         }
8204     }
8205 }
8206
8207
8208 /* Parse a game score from the character string "game", and
8209    record it as the history of the current game.  The game
8210    score is NOT assumed to start from the standard position.
8211    The display is not updated in any way.
8212    */
8213 void
8214 ParseGameHistory(game)
8215      char *game;
8216 {
8217     ChessMove moveType;
8218     int fromX, fromY, toX, toY, boardIndex;
8219     char promoChar;
8220     char *p, *q;
8221     char buf[MSG_SIZ];
8222
8223     if (appData.debugMode)
8224       fprintf(debugFP, "Parsing game history: %s\n", game);
8225
8226     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8227     gameInfo.site = StrSave(appData.icsHost);
8228     gameInfo.date = PGNDate();
8229     gameInfo.round = StrSave("-");
8230
8231     /* Parse out names of players */
8232     while (*game == ' ') game++;
8233     p = buf;
8234     while (*game != ' ') *p++ = *game++;
8235     *p = NULLCHAR;
8236     gameInfo.white = StrSave(buf);
8237     while (*game == ' ') game++;
8238     p = buf;
8239     while (*game != ' ' && *game != '\n') *p++ = *game++;
8240     *p = NULLCHAR;
8241     gameInfo.black = StrSave(buf);
8242
8243     /* Parse moves */
8244     boardIndex = blackPlaysFirst ? 1 : 0;
8245     yynewstr(game);
8246     for (;;) {
8247         yyboardindex = boardIndex;
8248         moveType = (ChessMove) Myylex();
8249         switch (moveType) {
8250           case IllegalMove:             /* maybe suicide chess, etc. */
8251   if (appData.debugMode) {
8252     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8253     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8254     setbuf(debugFP, NULL);
8255   }
8256           case WhitePromotion:
8257           case BlackPromotion:
8258           case WhiteNonPromotion:
8259           case BlackNonPromotion:
8260           case NormalMove:
8261           case WhiteCapturesEnPassant:
8262           case BlackCapturesEnPassant:
8263           case WhiteKingSideCastle:
8264           case WhiteQueenSideCastle:
8265           case BlackKingSideCastle:
8266           case BlackQueenSideCastle:
8267           case WhiteKingSideCastleWild:
8268           case WhiteQueenSideCastleWild:
8269           case BlackKingSideCastleWild:
8270           case BlackQueenSideCastleWild:
8271           /* PUSH Fabien */
8272           case WhiteHSideCastleFR:
8273           case WhiteASideCastleFR:
8274           case BlackHSideCastleFR:
8275           case BlackASideCastleFR:
8276           /* POP Fabien */
8277             fromX = currentMoveString[0] - AAA;
8278             fromY = currentMoveString[1] - ONE;
8279             toX = currentMoveString[2] - AAA;
8280             toY = currentMoveString[3] - ONE;
8281             promoChar = currentMoveString[4];
8282             break;
8283           case WhiteDrop:
8284           case BlackDrop:
8285             fromX = moveType == WhiteDrop ?
8286               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8287             (int) CharToPiece(ToLower(currentMoveString[0]));
8288             fromY = DROP_RANK;
8289             toX = currentMoveString[2] - AAA;
8290             toY = currentMoveString[3] - ONE;
8291             promoChar = NULLCHAR;
8292             break;
8293           case AmbiguousMove:
8294             /* bug? */
8295             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8296   if (appData.debugMode) {
8297     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8298     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8299     setbuf(debugFP, NULL);
8300   }
8301             DisplayError(buf, 0);
8302             return;
8303           case ImpossibleMove:
8304             /* bug? */
8305             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8306   if (appData.debugMode) {
8307     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8308     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8309     setbuf(debugFP, NULL);
8310   }
8311             DisplayError(buf, 0);
8312             return;
8313           case EndOfFile:
8314             if (boardIndex < backwardMostMove) {
8315                 /* Oops, gap.  How did that happen? */
8316                 DisplayError(_("Gap in move list"), 0);
8317                 return;
8318             }
8319             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8320             if (boardIndex > forwardMostMove) {
8321                 forwardMostMove = boardIndex;
8322             }
8323             return;
8324           case ElapsedTime:
8325             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8326                 strcat(parseList[boardIndex-1], " ");
8327                 strcat(parseList[boardIndex-1], yy_text);
8328             }
8329             continue;
8330           case Comment:
8331           case PGNTag:
8332           case NAG:
8333           default:
8334             /* ignore */
8335             continue;
8336           case WhiteWins:
8337           case BlackWins:
8338           case GameIsDrawn:
8339           case GameUnfinished:
8340             if (gameMode == IcsExamining) {
8341                 if (boardIndex < backwardMostMove) {
8342                     /* Oops, gap.  How did that happen? */
8343                     return;
8344                 }
8345                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8346                 return;
8347             }
8348             gameInfo.result = moveType;
8349             p = strchr(yy_text, '{');
8350             if (p == NULL) p = strchr(yy_text, '(');
8351             if (p == NULL) {
8352                 p = yy_text;
8353                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8354             } else {
8355                 q = strchr(p, *p == '{' ? '}' : ')');
8356                 if (q != NULL) *q = NULLCHAR;
8357                 p++;
8358             }
8359             gameInfo.resultDetails = StrSave(p);
8360             continue;
8361         }
8362         if (boardIndex >= forwardMostMove &&
8363             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8364             backwardMostMove = blackPlaysFirst ? 1 : 0;
8365             return;
8366         }
8367         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8368                                  fromY, fromX, toY, toX, promoChar,
8369                                  parseList[boardIndex]);
8370         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8371         /* currentMoveString is set as a side-effect of yylex */
8372         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8373         strcat(moveList[boardIndex], "\n");
8374         boardIndex++;
8375         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8376         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8377           case MT_NONE:
8378           case MT_STALEMATE:
8379           default:
8380             break;
8381           case MT_CHECK:
8382             if(gameInfo.variant != VariantShogi)
8383                 strcat(parseList[boardIndex - 1], "+");
8384             break;
8385           case MT_CHECKMATE:
8386           case MT_STAINMATE:
8387             strcat(parseList[boardIndex - 1], "#");
8388             break;
8389         }
8390     }
8391 }
8392
8393
8394 /* Apply a move to the given board  */
8395 void
8396 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8397      int fromX, fromY, toX, toY;
8398      int promoChar;
8399      Board board;
8400 {
8401   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8402   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8403
8404     /* [HGM] compute & store e.p. status and castling rights for new position */
8405     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8406
8407       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8408       oldEP = (signed char)board[EP_STATUS];
8409       board[EP_STATUS] = EP_NONE;
8410
8411       if( board[toY][toX] != EmptySquare )
8412            board[EP_STATUS] = EP_CAPTURE;
8413
8414   if (fromY == DROP_RANK) {
8415         /* must be first */
8416         piece = board[toY][toX] = (ChessSquare) fromX;
8417   } else {
8418       int i;
8419
8420       if( board[fromY][fromX] == WhitePawn ) {
8421            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8422                board[EP_STATUS] = EP_PAWN_MOVE;
8423            if( toY-fromY==2) {
8424                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8425                         gameInfo.variant != VariantBerolina || toX < fromX)
8426                       board[EP_STATUS] = toX | berolina;
8427                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8428                         gameInfo.variant != VariantBerolina || toX > fromX)
8429                       board[EP_STATUS] = toX;
8430            }
8431       } else
8432       if( board[fromY][fromX] == BlackPawn ) {
8433            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8434                board[EP_STATUS] = EP_PAWN_MOVE;
8435            if( toY-fromY== -2) {
8436                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8437                         gameInfo.variant != VariantBerolina || toX < fromX)
8438                       board[EP_STATUS] = toX | berolina;
8439                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8440                         gameInfo.variant != VariantBerolina || toX > fromX)
8441                       board[EP_STATUS] = toX;
8442            }
8443        }
8444
8445        for(i=0; i<nrCastlingRights; i++) {
8446            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8447               board[CASTLING][i] == toX   && castlingRank[i] == toY
8448              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8449        }
8450
8451      if (fromX == toX && fromY == toY) return;
8452
8453      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8454      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8455      if(gameInfo.variant == VariantKnightmate)
8456          king += (int) WhiteUnicorn - (int) WhiteKing;
8457
8458     /* Code added by Tord: */
8459     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8460     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8461         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8462       board[fromY][fromX] = EmptySquare;
8463       board[toY][toX] = EmptySquare;
8464       if((toX > fromX) != (piece == WhiteRook)) {
8465         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8466       } else {
8467         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8468       }
8469     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8470                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8471       board[fromY][fromX] = EmptySquare;
8472       board[toY][toX] = EmptySquare;
8473       if((toX > fromX) != (piece == BlackRook)) {
8474         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8475       } else {
8476         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8477       }
8478     /* End of code added by Tord */
8479
8480     } else if (board[fromY][fromX] == king
8481         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8482         && toY == fromY && toX > fromX+1) {
8483         board[fromY][fromX] = EmptySquare;
8484         board[toY][toX] = king;
8485         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8486         board[fromY][BOARD_RGHT-1] = EmptySquare;
8487     } else if (board[fromY][fromX] == king
8488         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8489                && toY == fromY && toX < fromX-1) {
8490         board[fromY][fromX] = EmptySquare;
8491         board[toY][toX] = king;
8492         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8493         board[fromY][BOARD_LEFT] = EmptySquare;
8494     } else if (board[fromY][fromX] == WhitePawn
8495                && toY >= BOARD_HEIGHT-promoRank
8496                && gameInfo.variant != VariantXiangqi
8497                ) {
8498         /* white pawn promotion */
8499         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8500         if (board[toY][toX] == EmptySquare) {
8501             board[toY][toX] = WhiteQueen;
8502         }
8503         if(gameInfo.variant==VariantBughouse ||
8504            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8505             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8506         board[fromY][fromX] = EmptySquare;
8507     } else if ((fromY == BOARD_HEIGHT-4)
8508                && (toX != fromX)
8509                && gameInfo.variant != VariantXiangqi
8510                && gameInfo.variant != VariantBerolina
8511                && (board[fromY][fromX] == WhitePawn)
8512                && (board[toY][toX] == EmptySquare)) {
8513         board[fromY][fromX] = EmptySquare;
8514         board[toY][toX] = WhitePawn;
8515         captured = board[toY - 1][toX];
8516         board[toY - 1][toX] = EmptySquare;
8517     } else if ((fromY == BOARD_HEIGHT-4)
8518                && (toX == fromX)
8519                && gameInfo.variant == VariantBerolina
8520                && (board[fromY][fromX] == WhitePawn)
8521                && (board[toY][toX] == EmptySquare)) {
8522         board[fromY][fromX] = EmptySquare;
8523         board[toY][toX] = WhitePawn;
8524         if(oldEP & EP_BEROLIN_A) {
8525                 captured = board[fromY][fromX-1];
8526                 board[fromY][fromX-1] = EmptySquare;
8527         }else{  captured = board[fromY][fromX+1];
8528                 board[fromY][fromX+1] = EmptySquare;
8529         }
8530     } else if (board[fromY][fromX] == king
8531         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8532                && toY == fromY && toX > fromX+1) {
8533         board[fromY][fromX] = EmptySquare;
8534         board[toY][toX] = king;
8535         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8536         board[fromY][BOARD_RGHT-1] = EmptySquare;
8537     } else if (board[fromY][fromX] == king
8538         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8539                && toY == fromY && toX < fromX-1) {
8540         board[fromY][fromX] = EmptySquare;
8541         board[toY][toX] = king;
8542         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8543         board[fromY][BOARD_LEFT] = EmptySquare;
8544     } else if (fromY == 7 && fromX == 3
8545                && board[fromY][fromX] == BlackKing
8546                && toY == 7 && toX == 5) {
8547         board[fromY][fromX] = EmptySquare;
8548         board[toY][toX] = BlackKing;
8549         board[fromY][7] = EmptySquare;
8550         board[toY][4] = BlackRook;
8551     } else if (fromY == 7 && fromX == 3
8552                && board[fromY][fromX] == BlackKing
8553                && toY == 7 && toX == 1) {
8554         board[fromY][fromX] = EmptySquare;
8555         board[toY][toX] = BlackKing;
8556         board[fromY][0] = EmptySquare;
8557         board[toY][2] = BlackRook;
8558     } else if (board[fromY][fromX] == BlackPawn
8559                && toY < promoRank
8560                && gameInfo.variant != VariantXiangqi
8561                ) {
8562         /* black pawn promotion */
8563         board[toY][toX] = CharToPiece(ToLower(promoChar));
8564         if (board[toY][toX] == EmptySquare) {
8565             board[toY][toX] = BlackQueen;
8566         }
8567         if(gameInfo.variant==VariantBughouse ||
8568            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8569             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8570         board[fromY][fromX] = EmptySquare;
8571     } else if ((fromY == 3)
8572                && (toX != fromX)
8573                && gameInfo.variant != VariantXiangqi
8574                && gameInfo.variant != VariantBerolina
8575                && (board[fromY][fromX] == BlackPawn)
8576                && (board[toY][toX] == EmptySquare)) {
8577         board[fromY][fromX] = EmptySquare;
8578         board[toY][toX] = BlackPawn;
8579         captured = board[toY + 1][toX];
8580         board[toY + 1][toX] = EmptySquare;
8581     } else if ((fromY == 3)
8582                && (toX == fromX)
8583                && gameInfo.variant == VariantBerolina
8584                && (board[fromY][fromX] == BlackPawn)
8585                && (board[toY][toX] == EmptySquare)) {
8586         board[fromY][fromX] = EmptySquare;
8587         board[toY][toX] = BlackPawn;
8588         if(oldEP & EP_BEROLIN_A) {
8589                 captured = board[fromY][fromX-1];
8590                 board[fromY][fromX-1] = EmptySquare;
8591         }else{  captured = board[fromY][fromX+1];
8592                 board[fromY][fromX+1] = EmptySquare;
8593         }
8594     } else {
8595         board[toY][toX] = board[fromY][fromX];
8596         board[fromY][fromX] = EmptySquare;
8597     }
8598   }
8599
8600     if (gameInfo.holdingsWidth != 0) {
8601
8602       /* !!A lot more code needs to be written to support holdings  */
8603       /* [HGM] OK, so I have written it. Holdings are stored in the */
8604       /* penultimate board files, so they are automaticlly stored   */
8605       /* in the game history.                                       */
8606       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8607                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8608         /* Delete from holdings, by decreasing count */
8609         /* and erasing image if necessary            */
8610         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8611         if(p < (int) BlackPawn) { /* white drop */
8612              p -= (int)WhitePawn;
8613                  p = PieceToNumber((ChessSquare)p);
8614              if(p >= gameInfo.holdingsSize) p = 0;
8615              if(--board[p][BOARD_WIDTH-2] <= 0)
8616                   board[p][BOARD_WIDTH-1] = EmptySquare;
8617              if((int)board[p][BOARD_WIDTH-2] < 0)
8618                         board[p][BOARD_WIDTH-2] = 0;
8619         } else {                  /* black drop */
8620              p -= (int)BlackPawn;
8621                  p = PieceToNumber((ChessSquare)p);
8622              if(p >= gameInfo.holdingsSize) p = 0;
8623              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8624                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8625              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8626                         board[BOARD_HEIGHT-1-p][1] = 0;
8627         }
8628       }
8629       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8630           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8631         /* [HGM] holdings: Add to holdings, if holdings exist */
8632         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8633                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8634                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8635         }
8636         p = (int) captured;
8637         if (p >= (int) BlackPawn) {
8638           p -= (int)BlackPawn;
8639           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8640                   /* in Shogi restore piece to its original  first */
8641                   captured = (ChessSquare) (DEMOTED captured);
8642                   p = DEMOTED p;
8643           }
8644           p = PieceToNumber((ChessSquare)p);
8645           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8646           board[p][BOARD_WIDTH-2]++;
8647           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8648         } else {
8649           p -= (int)WhitePawn;
8650           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8651                   captured = (ChessSquare) (DEMOTED captured);
8652                   p = DEMOTED p;
8653           }
8654           p = PieceToNumber((ChessSquare)p);
8655           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8656           board[BOARD_HEIGHT-1-p][1]++;
8657           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8658         }
8659       }
8660     } else if (gameInfo.variant == VariantAtomic) {
8661       if (captured != EmptySquare) {
8662         int y, x;
8663         for (y = toY-1; y <= toY+1; y++) {
8664           for (x = toX-1; x <= toX+1; x++) {
8665             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8666                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8667               board[y][x] = EmptySquare;
8668             }
8669           }
8670         }
8671         board[toY][toX] = EmptySquare;
8672       }
8673     }
8674     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8675         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8676     } else
8677     if(promoChar == '+') {
8678         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8679         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8680     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8681         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8682     }
8683     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8684                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8685         // [HGM] superchess: take promotion piece out of holdings
8686         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8687         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8688             if(!--board[k][BOARD_WIDTH-2])
8689                 board[k][BOARD_WIDTH-1] = EmptySquare;
8690         } else {
8691             if(!--board[BOARD_HEIGHT-1-k][1])
8692                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8693         }
8694     }
8695
8696 }
8697
8698 /* Updates forwardMostMove */
8699 void
8700 MakeMove(fromX, fromY, toX, toY, promoChar)
8701      int fromX, fromY, toX, toY;
8702      int promoChar;
8703 {
8704 //    forwardMostMove++; // [HGM] bare: moved downstream
8705
8706     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8707         int timeLeft; static int lastLoadFlag=0; int king, piece;
8708         piece = boards[forwardMostMove][fromY][fromX];
8709         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8710         if(gameInfo.variant == VariantKnightmate)
8711             king += (int) WhiteUnicorn - (int) WhiteKing;
8712         if(forwardMostMove == 0) {
8713             if(blackPlaysFirst)
8714                 fprintf(serverMoves, "%s;", second.tidy);
8715             fprintf(serverMoves, "%s;", first.tidy);
8716             if(!blackPlaysFirst)
8717                 fprintf(serverMoves, "%s;", second.tidy);
8718         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8719         lastLoadFlag = loadFlag;
8720         // print base move
8721         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8722         // print castling suffix
8723         if( toY == fromY && piece == king ) {
8724             if(toX-fromX > 1)
8725                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8726             if(fromX-toX >1)
8727                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8728         }
8729         // e.p. suffix
8730         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8731              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8732              boards[forwardMostMove][toY][toX] == EmptySquare
8733              && fromX != toX && fromY != toY)
8734                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8735         // promotion suffix
8736         if(promoChar != NULLCHAR)
8737                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8738         if(!loadFlag) {
8739             fprintf(serverMoves, "/%d/%d",
8740                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8741             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8742             else                      timeLeft = blackTimeRemaining/1000;
8743             fprintf(serverMoves, "/%d", timeLeft);
8744         }
8745         fflush(serverMoves);
8746     }
8747
8748     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8749       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8750                         0, 1);
8751       return;
8752     }
8753     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8754     if (commentList[forwardMostMove+1] != NULL) {
8755         free(commentList[forwardMostMove+1]);
8756         commentList[forwardMostMove+1] = NULL;
8757     }
8758     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8759     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8760     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8761     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8762     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8763     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8764     gameInfo.result = GameUnfinished;
8765     if (gameInfo.resultDetails != NULL) {
8766         free(gameInfo.resultDetails);
8767         gameInfo.resultDetails = NULL;
8768     }
8769     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8770                               moveList[forwardMostMove - 1]);
8771     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8772                              PosFlags(forwardMostMove - 1),
8773                              fromY, fromX, toY, toX, promoChar,
8774                              parseList[forwardMostMove - 1]);
8775     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8776       case MT_NONE:
8777       case MT_STALEMATE:
8778       default:
8779         break;
8780       case MT_CHECK:
8781         if(gameInfo.variant != VariantShogi)
8782             strcat(parseList[forwardMostMove - 1], "+");
8783         break;
8784       case MT_CHECKMATE:
8785       case MT_STAINMATE:
8786         strcat(parseList[forwardMostMove - 1], "#");
8787         break;
8788     }
8789     if (appData.debugMode) {
8790         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8791     }
8792
8793 }
8794
8795 /* Updates currentMove if not pausing */
8796 void
8797 ShowMove(fromX, fromY, toX, toY)
8798 {
8799     int instant = (gameMode == PlayFromGameFile) ?
8800         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8801     if(appData.noGUI) return;
8802     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8803         if (!instant) {
8804             if (forwardMostMove == currentMove + 1) {
8805                 AnimateMove(boards[forwardMostMove - 1],
8806                             fromX, fromY, toX, toY);
8807             }
8808             if (appData.highlightLastMove) {
8809                 SetHighlights(fromX, fromY, toX, toY);
8810             }
8811         }
8812         currentMove = forwardMostMove;
8813     }
8814
8815     if (instant) return;
8816
8817     DisplayMove(currentMove - 1);
8818     DrawPosition(FALSE, boards[currentMove]);
8819     DisplayBothClocks();
8820     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8821 }
8822
8823 void SendEgtPath(ChessProgramState *cps)
8824 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8825         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8826
8827         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8828
8829         while(*p) {
8830             char c, *q = name+1, *r, *s;
8831
8832             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8833             while(*p && *p != ',') *q++ = *p++;
8834             *q++ = ':'; *q = 0;
8835             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8836                 strcmp(name, ",nalimov:") == 0 ) {
8837                 // take nalimov path from the menu-changeable option first, if it is defined
8838               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8839                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8840             } else
8841             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8842                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8843                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8844                 s = r = StrStr(s, ":") + 1; // beginning of path info
8845                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8846                 c = *r; *r = 0;             // temporarily null-terminate path info
8847                     *--q = 0;               // strip of trailig ':' from name
8848                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8849                 *r = c;
8850                 SendToProgram(buf,cps);     // send egtbpath command for this format
8851             }
8852             if(*p == ',') p++; // read away comma to position for next format name
8853         }
8854 }
8855
8856 void
8857 InitChessProgram(cps, setup)
8858      ChessProgramState *cps;
8859      int setup; /* [HGM] needed to setup FRC opening position */
8860 {
8861     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8862     if (appData.noChessProgram) return;
8863     hintRequested = FALSE;
8864     bookRequested = FALSE;
8865
8866     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8867     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8868     if(cps->memSize) { /* [HGM] memory */
8869       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8870         SendToProgram(buf, cps);
8871     }
8872     SendEgtPath(cps); /* [HGM] EGT */
8873     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8874       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8875         SendToProgram(buf, cps);
8876     }
8877
8878     SendToProgram(cps->initString, cps);
8879     if (gameInfo.variant != VariantNormal &&
8880         gameInfo.variant != VariantLoadable
8881         /* [HGM] also send variant if board size non-standard */
8882         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8883                                             ) {
8884       char *v = VariantName(gameInfo.variant);
8885       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8886         /* [HGM] in protocol 1 we have to assume all variants valid */
8887         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8888         DisplayFatalError(buf, 0, 1);
8889         return;
8890       }
8891
8892       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8893       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8894       if( gameInfo.variant == VariantXiangqi )
8895            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8896       if( gameInfo.variant == VariantShogi )
8897            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8898       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8899            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8900       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8901                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8902            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8903       if( gameInfo.variant == VariantCourier )
8904            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8905       if( gameInfo.variant == VariantSuper )
8906            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8907       if( gameInfo.variant == VariantGreat )
8908            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8909
8910       if(overruled) {
8911         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8912                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8913            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8914            if(StrStr(cps->variants, b) == NULL) {
8915                // specific sized variant not known, check if general sizing allowed
8916                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8917                    if(StrStr(cps->variants, "boardsize") == NULL) {
8918                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8919                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8920                        DisplayFatalError(buf, 0, 1);
8921                        return;
8922                    }
8923                    /* [HGM] here we really should compare with the maximum supported board size */
8924                }
8925            }
8926       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8927       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8928       SendToProgram(buf, cps);
8929     }
8930     currentlyInitializedVariant = gameInfo.variant;
8931
8932     /* [HGM] send opening position in FRC to first engine */
8933     if(setup) {
8934           SendToProgram("force\n", cps);
8935           SendBoard(cps, 0);
8936           /* engine is now in force mode! Set flag to wake it up after first move. */
8937           setboardSpoiledMachineBlack = 1;
8938     }
8939
8940     if (cps->sendICS) {
8941       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8942       SendToProgram(buf, cps);
8943     }
8944     cps->maybeThinking = FALSE;
8945     cps->offeredDraw = 0;
8946     if (!appData.icsActive) {
8947         SendTimeControl(cps, movesPerSession, timeControl,
8948                         timeIncrement, appData.searchDepth,
8949                         searchTime);
8950     }
8951     if (appData.showThinking
8952         // [HGM] thinking: four options require thinking output to be sent
8953         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8954                                 ) {
8955         SendToProgram("post\n", cps);
8956     }
8957     SendToProgram("hard\n", cps);
8958     if (!appData.ponderNextMove) {
8959         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8960            it without being sure what state we are in first.  "hard"
8961            is not a toggle, so that one is OK.
8962          */
8963         SendToProgram("easy\n", cps);
8964     }
8965     if (cps->usePing) {
8966       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8967       SendToProgram(buf, cps);
8968     }
8969     cps->initDone = TRUE;
8970 }
8971
8972
8973 void
8974 StartChessProgram(cps)
8975      ChessProgramState *cps;
8976 {
8977     char buf[MSG_SIZ];
8978     int err;
8979
8980     if (appData.noChessProgram) return;
8981     cps->initDone = FALSE;
8982
8983     if (strcmp(cps->host, "localhost") == 0) {
8984         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8985     } else if (*appData.remoteShell == NULLCHAR) {
8986         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8987     } else {
8988         if (*appData.remoteUser == NULLCHAR) {
8989           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8990                     cps->program);
8991         } else {
8992           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8993                     cps->host, appData.remoteUser, cps->program);
8994         }
8995         err = StartChildProcess(buf, "", &cps->pr);
8996     }
8997
8998     if (err != 0) {
8999       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9000         DisplayFatalError(buf, err, 1);
9001         cps->pr = NoProc;
9002         cps->isr = NULL;
9003         return;
9004     }
9005
9006     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9007     if (cps->protocolVersion > 1) {
9008       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9009       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9010       cps->comboCnt = 0;  //                and values of combo boxes
9011       SendToProgram(buf, cps);
9012     } else {
9013       SendToProgram("xboard\n", cps);
9014     }
9015 }
9016
9017
9018 void
9019 TwoMachinesEventIfReady P((void))
9020 {
9021   if (first.lastPing != first.lastPong) {
9022     DisplayMessage("", _("Waiting for first chess program"));
9023     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9024     return;
9025   }
9026   if (second.lastPing != second.lastPong) {
9027     DisplayMessage("", _("Waiting for second chess program"));
9028     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9029     return;
9030   }
9031   ThawUI();
9032   TwoMachinesEvent();
9033 }
9034
9035 void
9036 NextMatchGame P((void))
9037 {
9038     int index; /* [HGM] autoinc: step load index during match */
9039     Reset(FALSE, TRUE);
9040     if (*appData.loadGameFile != NULLCHAR) {
9041         index = appData.loadGameIndex;
9042         if(index < 0) { // [HGM] autoinc
9043             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9044             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9045         }
9046         LoadGameFromFile(appData.loadGameFile,
9047                          index,
9048                          appData.loadGameFile, FALSE);
9049     } else if (*appData.loadPositionFile != NULLCHAR) {
9050         index = appData.loadPositionIndex;
9051         if(index < 0) { // [HGM] autoinc
9052             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9053             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9054         }
9055         LoadPositionFromFile(appData.loadPositionFile,
9056                              index,
9057                              appData.loadPositionFile);
9058     }
9059     TwoMachinesEventIfReady();
9060 }
9061
9062 void UserAdjudicationEvent( int result )
9063 {
9064     ChessMove gameResult = GameIsDrawn;
9065
9066     if( result > 0 ) {
9067         gameResult = WhiteWins;
9068     }
9069     else if( result < 0 ) {
9070         gameResult = BlackWins;
9071     }
9072
9073     if( gameMode == TwoMachinesPlay ) {
9074         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9075     }
9076 }
9077
9078
9079 // [HGM] save: calculate checksum of game to make games easily identifiable
9080 int StringCheckSum(char *s)
9081 {
9082         int i = 0;
9083         if(s==NULL) return 0;
9084         while(*s) i = i*259 + *s++;
9085         return i;
9086 }
9087
9088 int GameCheckSum()
9089 {
9090         int i, sum=0;
9091         for(i=backwardMostMove; i<forwardMostMove; i++) {
9092                 sum += pvInfoList[i].depth;
9093                 sum += StringCheckSum(parseList[i]);
9094                 sum += StringCheckSum(commentList[i]);
9095                 sum *= 261;
9096         }
9097         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9098         return sum + StringCheckSum(commentList[i]);
9099 } // end of save patch
9100
9101 void
9102 GameEnds(result, resultDetails, whosays)
9103      ChessMove result;
9104      char *resultDetails;
9105      int whosays;
9106 {
9107     GameMode nextGameMode;
9108     int isIcsGame;
9109     char buf[MSG_SIZ], popupRequested = 0;
9110
9111     if(endingGame) return; /* [HGM] crash: forbid recursion */
9112     endingGame = 1;
9113     if(twoBoards) { // [HGM] dual: switch back to one board
9114         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9115         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9116     }
9117     if (appData.debugMode) {
9118       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9119               result, resultDetails ? resultDetails : "(null)", whosays);
9120     }
9121
9122     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9123
9124     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9125         /* If we are playing on ICS, the server decides when the
9126            game is over, but the engine can offer to draw, claim
9127            a draw, or resign.
9128          */
9129 #if ZIPPY
9130         if (appData.zippyPlay && first.initDone) {
9131             if (result == GameIsDrawn) {
9132                 /* In case draw still needs to be claimed */
9133                 SendToICS(ics_prefix);
9134                 SendToICS("draw\n");
9135             } else if (StrCaseStr(resultDetails, "resign")) {
9136                 SendToICS(ics_prefix);
9137                 SendToICS("resign\n");
9138             }
9139         }
9140 #endif
9141         endingGame = 0; /* [HGM] crash */
9142         return;
9143     }
9144
9145     /* If we're loading the game from a file, stop */
9146     if (whosays == GE_FILE) {
9147       (void) StopLoadGameTimer();
9148       gameFileFP = NULL;
9149     }
9150
9151     /* Cancel draw offers */
9152     first.offeredDraw = second.offeredDraw = 0;
9153
9154     /* If this is an ICS game, only ICS can really say it's done;
9155        if not, anyone can. */
9156     isIcsGame = (gameMode == IcsPlayingWhite ||
9157                  gameMode == IcsPlayingBlack ||
9158                  gameMode == IcsObserving    ||
9159                  gameMode == IcsExamining);
9160
9161     if (!isIcsGame || whosays == GE_ICS) {
9162         /* OK -- not an ICS game, or ICS said it was done */
9163         StopClocks();
9164         if (!isIcsGame && !appData.noChessProgram)
9165           SetUserThinkingEnables();
9166
9167         /* [HGM] if a machine claims the game end we verify this claim */
9168         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9169             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9170                 char claimer;
9171                 ChessMove trueResult = (ChessMove) -1;
9172
9173                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9174                                             first.twoMachinesColor[0] :
9175                                             second.twoMachinesColor[0] ;
9176
9177                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9178                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9179                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9180                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9181                 } else
9182                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9183                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9184                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9185                 } else
9186                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9187                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9188                 }
9189
9190                 // now verify win claims, but not in drop games, as we don't understand those yet
9191                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9192                                                  || gameInfo.variant == VariantGreat) &&
9193                     (result == WhiteWins && claimer == 'w' ||
9194                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9195                       if (appData.debugMode) {
9196                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9197                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9198                       }
9199                       if(result != trueResult) {
9200                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9201                               result = claimer == 'w' ? BlackWins : WhiteWins;
9202                               resultDetails = buf;
9203                       }
9204                 } else
9205                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9206                     && (forwardMostMove <= backwardMostMove ||
9207                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9208                         (claimer=='b')==(forwardMostMove&1))
9209                                                                                   ) {
9210                       /* [HGM] verify: draws that were not flagged are false claims */
9211                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9212                       result = claimer == 'w' ? BlackWins : WhiteWins;
9213                       resultDetails = buf;
9214                 }
9215                 /* (Claiming a loss is accepted no questions asked!) */
9216             }
9217             /* [HGM] bare: don't allow bare King to win */
9218             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9219                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9220                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9221                && result != GameIsDrawn)
9222             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9223                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9224                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9225                         if(p >= 0 && p <= (int)WhiteKing) k++;
9226                 }
9227                 if (appData.debugMode) {
9228                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9229                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9230                 }
9231                 if(k <= 1) {
9232                         result = GameIsDrawn;
9233                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9234                         resultDetails = buf;
9235                 }
9236             }
9237         }
9238
9239
9240         if(serverMoves != NULL && !loadFlag) { char c = '=';
9241             if(result==WhiteWins) c = '+';
9242             if(result==BlackWins) c = '-';
9243             if(resultDetails != NULL)
9244                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9245         }
9246         if (resultDetails != NULL) {
9247             gameInfo.result = result;
9248             gameInfo.resultDetails = StrSave(resultDetails);
9249
9250             /* display last move only if game was not loaded from file */
9251             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9252                 DisplayMove(currentMove - 1);
9253
9254             if (forwardMostMove != 0) {
9255                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9256                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9257                                                                 ) {
9258                     if (*appData.saveGameFile != NULLCHAR) {
9259                         SaveGameToFile(appData.saveGameFile, TRUE);
9260                     } else if (appData.autoSaveGames) {
9261                         AutoSaveGame();
9262                     }
9263                     if (*appData.savePositionFile != NULLCHAR) {
9264                         SavePositionToFile(appData.savePositionFile);
9265                     }
9266                 }
9267             }
9268
9269             /* Tell program how game ended in case it is learning */
9270             /* [HGM] Moved this to after saving the PGN, just in case */
9271             /* engine died and we got here through time loss. In that */
9272             /* case we will get a fatal error writing the pipe, which */
9273             /* would otherwise lose us the PGN.                       */
9274             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9275             /* output during GameEnds should never be fatal anymore   */
9276             if (gameMode == MachinePlaysWhite ||
9277                 gameMode == MachinePlaysBlack ||
9278                 gameMode == TwoMachinesPlay ||
9279                 gameMode == IcsPlayingWhite ||
9280                 gameMode == IcsPlayingBlack ||
9281                 gameMode == BeginningOfGame) {
9282                 char buf[MSG_SIZ];
9283                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9284                         resultDetails);
9285                 if (first.pr != NoProc) {
9286                     SendToProgram(buf, &first);
9287                 }
9288                 if (second.pr != NoProc &&
9289                     gameMode == TwoMachinesPlay) {
9290                     SendToProgram(buf, &second);
9291                 }
9292             }
9293         }
9294
9295         if (appData.icsActive) {
9296             if (appData.quietPlay &&
9297                 (gameMode == IcsPlayingWhite ||
9298                  gameMode == IcsPlayingBlack)) {
9299                 SendToICS(ics_prefix);
9300                 SendToICS("set shout 1\n");
9301             }
9302             nextGameMode = IcsIdle;
9303             ics_user_moved = FALSE;
9304             /* clean up premove.  It's ugly when the game has ended and the
9305              * premove highlights are still on the board.
9306              */
9307             if (gotPremove) {
9308               gotPremove = FALSE;
9309               ClearPremoveHighlights();
9310               DrawPosition(FALSE, boards[currentMove]);
9311             }
9312             if (whosays == GE_ICS) {
9313                 switch (result) {
9314                 case WhiteWins:
9315                     if (gameMode == IcsPlayingWhite)
9316                         PlayIcsWinSound();
9317                     else if(gameMode == IcsPlayingBlack)
9318                         PlayIcsLossSound();
9319                     break;
9320                 case BlackWins:
9321                     if (gameMode == IcsPlayingBlack)
9322                         PlayIcsWinSound();
9323                     else if(gameMode == IcsPlayingWhite)
9324                         PlayIcsLossSound();
9325                     break;
9326                 case GameIsDrawn:
9327                     PlayIcsDrawSound();
9328                     break;
9329                 default:
9330                     PlayIcsUnfinishedSound();
9331                 }
9332             }
9333         } else if (gameMode == EditGame ||
9334                    gameMode == PlayFromGameFile ||
9335                    gameMode == AnalyzeMode ||
9336                    gameMode == AnalyzeFile) {
9337             nextGameMode = gameMode;
9338         } else {
9339             nextGameMode = EndOfGame;
9340         }
9341         pausing = FALSE;
9342         ModeHighlight();
9343     } else {
9344         nextGameMode = gameMode;
9345     }
9346
9347     if (appData.noChessProgram) {
9348         gameMode = nextGameMode;
9349         ModeHighlight();
9350         endingGame = 0; /* [HGM] crash */
9351         return;
9352     }
9353
9354     if (first.reuse) {
9355         /* Put first chess program into idle state */
9356         if (first.pr != NoProc &&
9357             (gameMode == MachinePlaysWhite ||
9358              gameMode == MachinePlaysBlack ||
9359              gameMode == TwoMachinesPlay ||
9360              gameMode == IcsPlayingWhite ||
9361              gameMode == IcsPlayingBlack ||
9362              gameMode == BeginningOfGame)) {
9363             SendToProgram("force\n", &first);
9364             if (first.usePing) {
9365               char buf[MSG_SIZ];
9366               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9367               SendToProgram(buf, &first);
9368             }
9369         }
9370     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9371         /* Kill off first chess program */
9372         if (first.isr != NULL)
9373           RemoveInputSource(first.isr);
9374         first.isr = NULL;
9375
9376         if (first.pr != NoProc) {
9377             ExitAnalyzeMode();
9378             DoSleep( appData.delayBeforeQuit );
9379             SendToProgram("quit\n", &first);
9380             DoSleep( appData.delayAfterQuit );
9381             DestroyChildProcess(first.pr, first.useSigterm);
9382         }
9383         first.pr = NoProc;
9384     }
9385     if (second.reuse) {
9386         /* Put second chess program into idle state */
9387         if (second.pr != NoProc &&
9388             gameMode == TwoMachinesPlay) {
9389             SendToProgram("force\n", &second);
9390             if (second.usePing) {
9391               char buf[MSG_SIZ];
9392               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9393               SendToProgram(buf, &second);
9394             }
9395         }
9396     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9397         /* Kill off second chess program */
9398         if (second.isr != NULL)
9399           RemoveInputSource(second.isr);
9400         second.isr = NULL;
9401
9402         if (second.pr != NoProc) {
9403             DoSleep( appData.delayBeforeQuit );
9404             SendToProgram("quit\n", &second);
9405             DoSleep( appData.delayAfterQuit );
9406             DestroyChildProcess(second.pr, second.useSigterm);
9407         }
9408         second.pr = NoProc;
9409     }
9410
9411     if (matchMode && gameMode == TwoMachinesPlay) {
9412         switch (result) {
9413         case WhiteWins:
9414           if (first.twoMachinesColor[0] == 'w') {
9415             first.matchWins++;
9416           } else {
9417             second.matchWins++;
9418           }
9419           break;
9420         case BlackWins:
9421           if (first.twoMachinesColor[0] == 'b') {
9422             first.matchWins++;
9423           } else {
9424             second.matchWins++;
9425           }
9426           break;
9427         default:
9428           break;
9429         }
9430         if (matchGame < appData.matchGames) {
9431             char *tmp;
9432             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9433                 tmp = first.twoMachinesColor;
9434                 first.twoMachinesColor = second.twoMachinesColor;
9435                 second.twoMachinesColor = tmp;
9436             }
9437             gameMode = nextGameMode;
9438             matchGame++;
9439             if(appData.matchPause>10000 || appData.matchPause<10)
9440                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9441             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9442             endingGame = 0; /* [HGM] crash */
9443             return;
9444         } else {
9445             gameMode = nextGameMode;
9446             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9447                      first.tidy, second.tidy,
9448                      first.matchWins, second.matchWins,
9449                      appData.matchGames - (first.matchWins + second.matchWins));
9450             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9451         }
9452     }
9453     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9454         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9455       ExitAnalyzeMode();
9456     gameMode = nextGameMode;
9457     ModeHighlight();
9458     endingGame = 0;  /* [HGM] crash */
9459     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9460       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9461         matchMode = FALSE; appData.matchGames = matchGame = 0;
9462         DisplayNote(buf);
9463       }
9464     }
9465 }
9466
9467 /* Assumes program was just initialized (initString sent).
9468    Leaves program in force mode. */
9469 void
9470 FeedMovesToProgram(cps, upto)
9471      ChessProgramState *cps;
9472      int upto;
9473 {
9474     int i;
9475
9476     if (appData.debugMode)
9477       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9478               startedFromSetupPosition ? "position and " : "",
9479               backwardMostMove, upto, cps->which);
9480     if(currentlyInitializedVariant != gameInfo.variant) {
9481       char buf[MSG_SIZ];
9482         // [HGM] variantswitch: make engine aware of new variant
9483         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9484                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9485         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9486         SendToProgram(buf, cps);
9487         currentlyInitializedVariant = gameInfo.variant;
9488     }
9489     SendToProgram("force\n", cps);
9490     if (startedFromSetupPosition) {
9491         SendBoard(cps, backwardMostMove);
9492     if (appData.debugMode) {
9493         fprintf(debugFP, "feedMoves\n");
9494     }
9495     }
9496     for (i = backwardMostMove; i < upto; i++) {
9497         SendMoveToProgram(i, cps);
9498     }
9499 }
9500
9501
9502 void
9503 ResurrectChessProgram()
9504 {
9505      /* The chess program may have exited.
9506         If so, restart it and feed it all the moves made so far. */
9507
9508     if (appData.noChessProgram || first.pr != NoProc) return;
9509
9510     StartChessProgram(&first);
9511     InitChessProgram(&first, FALSE);
9512     FeedMovesToProgram(&first, currentMove);
9513
9514     if (!first.sendTime) {
9515         /* can't tell gnuchess what its clock should read,
9516            so we bow to its notion. */
9517         ResetClocks();
9518         timeRemaining[0][currentMove] = whiteTimeRemaining;
9519         timeRemaining[1][currentMove] = blackTimeRemaining;
9520     }
9521
9522     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9523                 appData.icsEngineAnalyze) && first.analysisSupport) {
9524       SendToProgram("analyze\n", &first);
9525       first.analyzing = TRUE;
9526     }
9527 }
9528
9529 /*
9530  * Button procedures
9531  */
9532 void
9533 Reset(redraw, init)
9534      int redraw, init;
9535 {
9536     int i;
9537
9538     if (appData.debugMode) {
9539         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9540                 redraw, init, gameMode);
9541     }
9542     CleanupTail(); // [HGM] vari: delete any stored variations
9543     pausing = pauseExamInvalid = FALSE;
9544     startedFromSetupPosition = blackPlaysFirst = FALSE;
9545     firstMove = TRUE;
9546     whiteFlag = blackFlag = FALSE;
9547     userOfferedDraw = FALSE;
9548     hintRequested = bookRequested = FALSE;
9549     first.maybeThinking = FALSE;
9550     second.maybeThinking = FALSE;
9551     first.bookSuspend = FALSE; // [HGM] book
9552     second.bookSuspend = FALSE;
9553     thinkOutput[0] = NULLCHAR;
9554     lastHint[0] = NULLCHAR;
9555     ClearGameInfo(&gameInfo);
9556     gameInfo.variant = StringToVariant(appData.variant);
9557     ics_user_moved = ics_clock_paused = FALSE;
9558     ics_getting_history = H_FALSE;
9559     ics_gamenum = -1;
9560     white_holding[0] = black_holding[0] = NULLCHAR;
9561     ClearProgramStats();
9562     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9563
9564     ResetFrontEnd();
9565     ClearHighlights();
9566     flipView = appData.flipView;
9567     ClearPremoveHighlights();
9568     gotPremove = FALSE;
9569     alarmSounded = FALSE;
9570
9571     GameEnds(EndOfFile, NULL, GE_PLAYER);
9572     if(appData.serverMovesName != NULL) {
9573         /* [HGM] prepare to make moves file for broadcasting */
9574         clock_t t = clock();
9575         if(serverMoves != NULL) fclose(serverMoves);
9576         serverMoves = fopen(appData.serverMovesName, "r");
9577         if(serverMoves != NULL) {
9578             fclose(serverMoves);
9579             /* delay 15 sec before overwriting, so all clients can see end */
9580             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9581         }
9582         serverMoves = fopen(appData.serverMovesName, "w");
9583     }
9584
9585     ExitAnalyzeMode();
9586     gameMode = BeginningOfGame;
9587     ModeHighlight();
9588     if(appData.icsActive) gameInfo.variant = VariantNormal;
9589     currentMove = forwardMostMove = backwardMostMove = 0;
9590     InitPosition(redraw);
9591     for (i = 0; i < MAX_MOVES; i++) {
9592         if (commentList[i] != NULL) {
9593             free(commentList[i]);
9594             commentList[i] = NULL;
9595         }
9596     }
9597     ResetClocks();
9598     timeRemaining[0][0] = whiteTimeRemaining;
9599     timeRemaining[1][0] = blackTimeRemaining;
9600     if (first.pr == NULL) {
9601         StartChessProgram(&first);
9602     }
9603     if (init) {
9604             InitChessProgram(&first, startedFromSetupPosition);
9605     }
9606     DisplayTitle("");
9607     DisplayMessage("", "");
9608     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9609     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9610 }
9611
9612 void
9613 AutoPlayGameLoop()
9614 {
9615     for (;;) {
9616         if (!AutoPlayOneMove())
9617           return;
9618         if (matchMode || appData.timeDelay == 0)
9619           continue;
9620         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9621           return;
9622         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9623         break;
9624     }
9625 }
9626
9627
9628 int
9629 AutoPlayOneMove()
9630 {
9631     int fromX, fromY, toX, toY;
9632
9633     if (appData.debugMode) {
9634       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9635     }
9636
9637     if (gameMode != PlayFromGameFile)
9638       return FALSE;
9639
9640     if (currentMove >= forwardMostMove) {
9641       gameMode = EditGame;
9642       ModeHighlight();
9643
9644       /* [AS] Clear current move marker at the end of a game */
9645       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9646
9647       return FALSE;
9648     }
9649
9650     toX = moveList[currentMove][2] - AAA;
9651     toY = moveList[currentMove][3] - ONE;
9652
9653     if (moveList[currentMove][1] == '@') {
9654         if (appData.highlightLastMove) {
9655             SetHighlights(-1, -1, toX, toY);
9656         }
9657     } else {
9658         fromX = moveList[currentMove][0] - AAA;
9659         fromY = moveList[currentMove][1] - ONE;
9660
9661         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9662
9663         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9664
9665         if (appData.highlightLastMove) {
9666             SetHighlights(fromX, fromY, toX, toY);
9667         }
9668     }
9669     DisplayMove(currentMove);
9670     SendMoveToProgram(currentMove++, &first);
9671     DisplayBothClocks();
9672     DrawPosition(FALSE, boards[currentMove]);
9673     // [HGM] PV info: always display, routine tests if empty
9674     DisplayComment(currentMove - 1, commentList[currentMove]);
9675     return TRUE;
9676 }
9677
9678
9679 int
9680 LoadGameOneMove(readAhead)
9681      ChessMove readAhead;
9682 {
9683     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9684     char promoChar = NULLCHAR;
9685     ChessMove moveType;
9686     char move[MSG_SIZ];
9687     char *p, *q;
9688
9689     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9690         gameMode != AnalyzeMode && gameMode != Training) {
9691         gameFileFP = NULL;
9692         return FALSE;
9693     }
9694
9695     yyboardindex = forwardMostMove;
9696     if (readAhead != EndOfFile) {
9697       moveType = readAhead;
9698     } else {
9699       if (gameFileFP == NULL)
9700           return FALSE;
9701       moveType = (ChessMove) Myylex();
9702     }
9703
9704     done = FALSE;
9705     switch (moveType) {
9706       case Comment:
9707         if (appData.debugMode)
9708           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9709         p = yy_text;
9710
9711         /* append the comment but don't display it */
9712         AppendComment(currentMove, p, FALSE);
9713         return TRUE;
9714
9715       case WhiteCapturesEnPassant:
9716       case BlackCapturesEnPassant:
9717       case WhitePromotion:
9718       case BlackPromotion:
9719       case WhiteNonPromotion:
9720       case BlackNonPromotion:
9721       case NormalMove:
9722       case WhiteKingSideCastle:
9723       case WhiteQueenSideCastle:
9724       case BlackKingSideCastle:
9725       case BlackQueenSideCastle:
9726       case WhiteKingSideCastleWild:
9727       case WhiteQueenSideCastleWild:
9728       case BlackKingSideCastleWild:
9729       case BlackQueenSideCastleWild:
9730       /* PUSH Fabien */
9731       case WhiteHSideCastleFR:
9732       case WhiteASideCastleFR:
9733       case BlackHSideCastleFR:
9734       case BlackASideCastleFR:
9735       /* POP Fabien */
9736         if (appData.debugMode)
9737           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9738         fromX = currentMoveString[0] - AAA;
9739         fromY = currentMoveString[1] - ONE;
9740         toX = currentMoveString[2] - AAA;
9741         toY = currentMoveString[3] - ONE;
9742         promoChar = currentMoveString[4];
9743         break;
9744
9745       case WhiteDrop:
9746       case BlackDrop:
9747         if (appData.debugMode)
9748           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9749         fromX = moveType == WhiteDrop ?
9750           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9751         (int) CharToPiece(ToLower(currentMoveString[0]));
9752         fromY = DROP_RANK;
9753         toX = currentMoveString[2] - AAA;
9754         toY = currentMoveString[3] - ONE;
9755         break;
9756
9757       case WhiteWins:
9758       case BlackWins:
9759       case GameIsDrawn:
9760       case GameUnfinished:
9761         if (appData.debugMode)
9762           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9763         p = strchr(yy_text, '{');
9764         if (p == NULL) p = strchr(yy_text, '(');
9765         if (p == NULL) {
9766             p = yy_text;
9767             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9768         } else {
9769             q = strchr(p, *p == '{' ? '}' : ')');
9770             if (q != NULL) *q = NULLCHAR;
9771             p++;
9772         }
9773         GameEnds(moveType, p, GE_FILE);
9774         done = TRUE;
9775         if (cmailMsgLoaded) {
9776             ClearHighlights();
9777             flipView = WhiteOnMove(currentMove);
9778             if (moveType == GameUnfinished) flipView = !flipView;
9779             if (appData.debugMode)
9780               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9781         }
9782         break;
9783
9784       case EndOfFile:
9785         if (appData.debugMode)
9786           fprintf(debugFP, "Parser hit end of file\n");
9787         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9788           case MT_NONE:
9789           case MT_CHECK:
9790             break;
9791           case MT_CHECKMATE:
9792           case MT_STAINMATE:
9793             if (WhiteOnMove(currentMove)) {
9794                 GameEnds(BlackWins, "Black mates", GE_FILE);
9795             } else {
9796                 GameEnds(WhiteWins, "White mates", GE_FILE);
9797             }
9798             break;
9799           case MT_STALEMATE:
9800             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9801             break;
9802         }
9803         done = TRUE;
9804         break;
9805
9806       case MoveNumberOne:
9807         if (lastLoadGameStart == GNUChessGame) {
9808             /* GNUChessGames have numbers, but they aren't move numbers */
9809             if (appData.debugMode)
9810               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9811                       yy_text, (int) moveType);
9812             return LoadGameOneMove(EndOfFile); /* tail recursion */
9813         }
9814         /* else fall thru */
9815
9816       case XBoardGame:
9817       case GNUChessGame:
9818       case PGNTag:
9819         /* Reached start of next game in file */
9820         if (appData.debugMode)
9821           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9822         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9823           case MT_NONE:
9824           case MT_CHECK:
9825             break;
9826           case MT_CHECKMATE:
9827           case MT_STAINMATE:
9828             if (WhiteOnMove(currentMove)) {
9829                 GameEnds(BlackWins, "Black mates", GE_FILE);
9830             } else {
9831                 GameEnds(WhiteWins, "White mates", GE_FILE);
9832             }
9833             break;
9834           case MT_STALEMATE:
9835             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9836             break;
9837         }
9838         done = TRUE;
9839         break;
9840
9841       case PositionDiagram:     /* should not happen; ignore */
9842       case ElapsedTime:         /* ignore */
9843       case NAG:                 /* ignore */
9844         if (appData.debugMode)
9845           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9846                   yy_text, (int) moveType);
9847         return LoadGameOneMove(EndOfFile); /* tail recursion */
9848
9849       case IllegalMove:
9850         if (appData.testLegality) {
9851             if (appData.debugMode)
9852               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9853             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9854                     (forwardMostMove / 2) + 1,
9855                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9856             DisplayError(move, 0);
9857             done = TRUE;
9858         } else {
9859             if (appData.debugMode)
9860               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9861                       yy_text, currentMoveString);
9862             fromX = currentMoveString[0] - AAA;
9863             fromY = currentMoveString[1] - ONE;
9864             toX = currentMoveString[2] - AAA;
9865             toY = currentMoveString[3] - ONE;
9866             promoChar = currentMoveString[4];
9867         }
9868         break;
9869
9870       case AmbiguousMove:
9871         if (appData.debugMode)
9872           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9873         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9874                 (forwardMostMove / 2) + 1,
9875                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9876         DisplayError(move, 0);
9877         done = TRUE;
9878         break;
9879
9880       default:
9881       case ImpossibleMove:
9882         if (appData.debugMode)
9883           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9884         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9885                 (forwardMostMove / 2) + 1,
9886                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9887         DisplayError(move, 0);
9888         done = TRUE;
9889         break;
9890     }
9891
9892     if (done) {
9893         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9894             DrawPosition(FALSE, boards[currentMove]);
9895             DisplayBothClocks();
9896             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9897               DisplayComment(currentMove - 1, commentList[currentMove]);
9898         }
9899         (void) StopLoadGameTimer();
9900         gameFileFP = NULL;
9901         cmailOldMove = forwardMostMove;
9902         return FALSE;
9903     } else {
9904         /* currentMoveString is set as a side-effect of yylex */
9905         strcat(currentMoveString, "\n");
9906         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9907
9908         thinkOutput[0] = NULLCHAR;
9909         MakeMove(fromX, fromY, toX, toY, promoChar);
9910         currentMove = forwardMostMove;
9911         return TRUE;
9912     }
9913 }
9914
9915 /* Load the nth game from the given file */
9916 int
9917 LoadGameFromFile(filename, n, title, useList)
9918      char *filename;
9919      int n;
9920      char *title;
9921      /*Boolean*/ int useList;
9922 {
9923     FILE *f;
9924     char buf[MSG_SIZ];
9925
9926     if (strcmp(filename, "-") == 0) {
9927         f = stdin;
9928         title = "stdin";
9929     } else {
9930         f = fopen(filename, "rb");
9931         if (f == NULL) {
9932           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9933             DisplayError(buf, errno);
9934             return FALSE;
9935         }
9936     }
9937     if (fseek(f, 0, 0) == -1) {
9938         /* f is not seekable; probably a pipe */
9939         useList = FALSE;
9940     }
9941     if (useList && n == 0) {
9942         int error = GameListBuild(f);
9943         if (error) {
9944             DisplayError(_("Cannot build game list"), error);
9945         } else if (!ListEmpty(&gameList) &&
9946                    ((ListGame *) gameList.tailPred)->number > 1) {
9947             GameListPopUp(f, title);
9948             return TRUE;
9949         }
9950         GameListDestroy();
9951         n = 1;
9952     }
9953     if (n == 0) n = 1;
9954     return LoadGame(f, n, title, FALSE);
9955 }
9956
9957
9958 void
9959 MakeRegisteredMove()
9960 {
9961     int fromX, fromY, toX, toY;
9962     char promoChar;
9963     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9964         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9965           case CMAIL_MOVE:
9966           case CMAIL_DRAW:
9967             if (appData.debugMode)
9968               fprintf(debugFP, "Restoring %s for game %d\n",
9969                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9970
9971             thinkOutput[0] = NULLCHAR;
9972             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9973             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9974             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9975             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9976             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9977             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9978             MakeMove(fromX, fromY, toX, toY, promoChar);
9979             ShowMove(fromX, fromY, toX, toY);
9980
9981             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9982               case MT_NONE:
9983               case MT_CHECK:
9984                 break;
9985
9986               case MT_CHECKMATE:
9987               case MT_STAINMATE:
9988                 if (WhiteOnMove(currentMove)) {
9989                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9990                 } else {
9991                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9992                 }
9993                 break;
9994
9995               case MT_STALEMATE:
9996                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9997                 break;
9998             }
9999
10000             break;
10001
10002           case CMAIL_RESIGN:
10003             if (WhiteOnMove(currentMove)) {
10004                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10005             } else {
10006                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10007             }
10008             break;
10009
10010           case CMAIL_ACCEPT:
10011             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10012             break;
10013
10014           default:
10015             break;
10016         }
10017     }
10018
10019     return;
10020 }
10021
10022 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10023 int
10024 CmailLoadGame(f, gameNumber, title, useList)
10025      FILE *f;
10026      int gameNumber;
10027      char *title;
10028      int useList;
10029 {
10030     int retVal;
10031
10032     if (gameNumber > nCmailGames) {
10033         DisplayError(_("No more games in this message"), 0);
10034         return FALSE;
10035     }
10036     if (f == lastLoadGameFP) {
10037         int offset = gameNumber - lastLoadGameNumber;
10038         if (offset == 0) {
10039             cmailMsg[0] = NULLCHAR;
10040             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10041                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10042                 nCmailMovesRegistered--;
10043             }
10044             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10045             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10046                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10047             }
10048         } else {
10049             if (! RegisterMove()) return FALSE;
10050         }
10051     }
10052
10053     retVal = LoadGame(f, gameNumber, title, useList);
10054
10055     /* Make move registered during previous look at this game, if any */
10056     MakeRegisteredMove();
10057
10058     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10059         commentList[currentMove]
10060           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10061         DisplayComment(currentMove - 1, commentList[currentMove]);
10062     }
10063
10064     return retVal;
10065 }
10066
10067 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10068 int
10069 ReloadGame(offset)
10070      int offset;
10071 {
10072     int gameNumber = lastLoadGameNumber + offset;
10073     if (lastLoadGameFP == NULL) {
10074         DisplayError(_("No game has been loaded yet"), 0);
10075         return FALSE;
10076     }
10077     if (gameNumber <= 0) {
10078         DisplayError(_("Can't back up any further"), 0);
10079         return FALSE;
10080     }
10081     if (cmailMsgLoaded) {
10082         return CmailLoadGame(lastLoadGameFP, gameNumber,
10083                              lastLoadGameTitle, lastLoadGameUseList);
10084     } else {
10085         return LoadGame(lastLoadGameFP, gameNumber,
10086                         lastLoadGameTitle, lastLoadGameUseList);
10087     }
10088 }
10089
10090
10091
10092 /* Load the nth game from open file f */
10093 int
10094 LoadGame(f, gameNumber, title, useList)
10095      FILE *f;
10096      int gameNumber;
10097      char *title;
10098      int useList;
10099 {
10100     ChessMove cm;
10101     char buf[MSG_SIZ];
10102     int gn = gameNumber;
10103     ListGame *lg = NULL;
10104     int numPGNTags = 0;
10105     int err;
10106     GameMode oldGameMode;
10107     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10108
10109     if (appData.debugMode)
10110         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10111
10112     if (gameMode == Training )
10113         SetTrainingModeOff();
10114
10115     oldGameMode = gameMode;
10116     if (gameMode != BeginningOfGame) {
10117       Reset(FALSE, TRUE);
10118     }
10119
10120     gameFileFP = f;
10121     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10122         fclose(lastLoadGameFP);
10123     }
10124
10125     if (useList) {
10126         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10127
10128         if (lg) {
10129             fseek(f, lg->offset, 0);
10130             GameListHighlight(gameNumber);
10131             gn = 1;
10132         }
10133         else {
10134             DisplayError(_("Game number out of range"), 0);
10135             return FALSE;
10136         }
10137     } else {
10138         GameListDestroy();
10139         if (fseek(f, 0, 0) == -1) {
10140             if (f == lastLoadGameFP ?
10141                 gameNumber == lastLoadGameNumber + 1 :
10142                 gameNumber == 1) {
10143                 gn = 1;
10144             } else {
10145                 DisplayError(_("Can't seek on game file"), 0);
10146                 return FALSE;
10147             }
10148         }
10149     }
10150     lastLoadGameFP = f;
10151     lastLoadGameNumber = gameNumber;
10152     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10153     lastLoadGameUseList = useList;
10154
10155     yynewfile(f);
10156
10157     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10158       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10159                 lg->gameInfo.black);
10160             DisplayTitle(buf);
10161     } else if (*title != NULLCHAR) {
10162         if (gameNumber > 1) {
10163           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10164             DisplayTitle(buf);
10165         } else {
10166             DisplayTitle(title);
10167         }
10168     }
10169
10170     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10171         gameMode = PlayFromGameFile;
10172         ModeHighlight();
10173     }
10174
10175     currentMove = forwardMostMove = backwardMostMove = 0;
10176     CopyBoard(boards[0], initialPosition);
10177     StopClocks();
10178
10179     /*
10180      * Skip the first gn-1 games in the file.
10181      * Also skip over anything that precedes an identifiable
10182      * start of game marker, to avoid being confused by
10183      * garbage at the start of the file.  Currently
10184      * recognized start of game markers are the move number "1",
10185      * the pattern "gnuchess .* game", the pattern
10186      * "^[#;%] [^ ]* game file", and a PGN tag block.
10187      * A game that starts with one of the latter two patterns
10188      * will also have a move number 1, possibly
10189      * following a position diagram.
10190      * 5-4-02: Let's try being more lenient and allowing a game to
10191      * start with an unnumbered move.  Does that break anything?
10192      */
10193     cm = lastLoadGameStart = EndOfFile;
10194     while (gn > 0) {
10195         yyboardindex = forwardMostMove;
10196         cm = (ChessMove) Myylex();
10197         switch (cm) {
10198           case EndOfFile:
10199             if (cmailMsgLoaded) {
10200                 nCmailGames = CMAIL_MAX_GAMES - gn;
10201             } else {
10202                 Reset(TRUE, TRUE);
10203                 DisplayError(_("Game not found in file"), 0);
10204             }
10205             return FALSE;
10206
10207           case GNUChessGame:
10208           case XBoardGame:
10209             gn--;
10210             lastLoadGameStart = cm;
10211             break;
10212
10213           case MoveNumberOne:
10214             switch (lastLoadGameStart) {
10215               case GNUChessGame:
10216               case XBoardGame:
10217               case PGNTag:
10218                 break;
10219               case MoveNumberOne:
10220               case EndOfFile:
10221                 gn--;           /* count this game */
10222                 lastLoadGameStart = cm;
10223                 break;
10224               default:
10225                 /* impossible */
10226                 break;
10227             }
10228             break;
10229
10230           case PGNTag:
10231             switch (lastLoadGameStart) {
10232               case GNUChessGame:
10233               case PGNTag:
10234               case MoveNumberOne:
10235               case EndOfFile:
10236                 gn--;           /* count this game */
10237                 lastLoadGameStart = cm;
10238                 break;
10239               case XBoardGame:
10240                 lastLoadGameStart = cm; /* game counted already */
10241                 break;
10242               default:
10243                 /* impossible */
10244                 break;
10245             }
10246             if (gn > 0) {
10247                 do {
10248                     yyboardindex = forwardMostMove;
10249                     cm = (ChessMove) Myylex();
10250                 } while (cm == PGNTag || cm == Comment);
10251             }
10252             break;
10253
10254           case WhiteWins:
10255           case BlackWins:
10256           case GameIsDrawn:
10257             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10258                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10259                     != CMAIL_OLD_RESULT) {
10260                     nCmailResults ++ ;
10261                     cmailResult[  CMAIL_MAX_GAMES
10262                                 - gn - 1] = CMAIL_OLD_RESULT;
10263                 }
10264             }
10265             break;
10266
10267           case NormalMove:
10268             /* Only a NormalMove can be at the start of a game
10269              * without a position diagram. */
10270             if (lastLoadGameStart == EndOfFile ) {
10271               gn--;
10272               lastLoadGameStart = MoveNumberOne;
10273             }
10274             break;
10275
10276           default:
10277             break;
10278         }
10279     }
10280
10281     if (appData.debugMode)
10282       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10283
10284     if (cm == XBoardGame) {
10285         /* Skip any header junk before position diagram and/or move 1 */
10286         for (;;) {
10287             yyboardindex = forwardMostMove;
10288             cm = (ChessMove) Myylex();
10289
10290             if (cm == EndOfFile ||
10291                 cm == GNUChessGame || cm == XBoardGame) {
10292                 /* Empty game; pretend end-of-file and handle later */
10293                 cm = EndOfFile;
10294                 break;
10295             }
10296
10297             if (cm == MoveNumberOne || cm == PositionDiagram ||
10298                 cm == PGNTag || cm == Comment)
10299               break;
10300         }
10301     } else if (cm == GNUChessGame) {
10302         if (gameInfo.event != NULL) {
10303             free(gameInfo.event);
10304         }
10305         gameInfo.event = StrSave(yy_text);
10306     }
10307
10308     startedFromSetupPosition = FALSE;
10309     while (cm == PGNTag) {
10310         if (appData.debugMode)
10311           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10312         err = ParsePGNTag(yy_text, &gameInfo);
10313         if (!err) numPGNTags++;
10314
10315         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10316         if(gameInfo.variant != oldVariant) {
10317             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10318             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10319             InitPosition(TRUE);
10320             oldVariant = gameInfo.variant;
10321             if (appData.debugMode)
10322               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10323         }
10324
10325
10326         if (gameInfo.fen != NULL) {
10327           Board initial_position;
10328           startedFromSetupPosition = TRUE;
10329           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10330             Reset(TRUE, TRUE);
10331             DisplayError(_("Bad FEN position in file"), 0);
10332             return FALSE;
10333           }
10334           CopyBoard(boards[0], initial_position);
10335           if (blackPlaysFirst) {
10336             currentMove = forwardMostMove = backwardMostMove = 1;
10337             CopyBoard(boards[1], initial_position);
10338             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10339             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10340             timeRemaining[0][1] = whiteTimeRemaining;
10341             timeRemaining[1][1] = blackTimeRemaining;
10342             if (commentList[0] != NULL) {
10343               commentList[1] = commentList[0];
10344               commentList[0] = NULL;
10345             }
10346           } else {
10347             currentMove = forwardMostMove = backwardMostMove = 0;
10348           }
10349           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10350           {   int i;
10351               initialRulePlies = FENrulePlies;
10352               for( i=0; i< nrCastlingRights; i++ )
10353                   initialRights[i] = initial_position[CASTLING][i];
10354           }
10355           yyboardindex = forwardMostMove;
10356           free(gameInfo.fen);
10357           gameInfo.fen = NULL;
10358         }
10359
10360         yyboardindex = forwardMostMove;
10361         cm = (ChessMove) Myylex();
10362
10363         /* Handle comments interspersed among the tags */
10364         while (cm == Comment) {
10365             char *p;
10366             if (appData.debugMode)
10367               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10368             p = yy_text;
10369             AppendComment(currentMove, p, FALSE);
10370             yyboardindex = forwardMostMove;
10371             cm = (ChessMove) Myylex();
10372         }
10373     }
10374
10375     /* don't rely on existence of Event tag since if game was
10376      * pasted from clipboard the Event tag may not exist
10377      */
10378     if (numPGNTags > 0){
10379         char *tags;
10380         if (gameInfo.variant == VariantNormal) {
10381           VariantClass v = StringToVariant(gameInfo.event);
10382           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10383           if(v < VariantShogi) gameInfo.variant = v;
10384         }
10385         if (!matchMode) {
10386           if( appData.autoDisplayTags ) {
10387             tags = PGNTags(&gameInfo);
10388             TagsPopUp(tags, CmailMsg());
10389             free(tags);
10390           }
10391         }
10392     } else {
10393         /* Make something up, but don't display it now */
10394         SetGameInfo();
10395         TagsPopDown();
10396     }
10397
10398     if (cm == PositionDiagram) {
10399         int i, j;
10400         char *p;
10401         Board initial_position;
10402
10403         if (appData.debugMode)
10404           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10405
10406         if (!startedFromSetupPosition) {
10407             p = yy_text;
10408             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10409               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10410                 switch (*p) {
10411                   case '[':
10412                   case '-':
10413                   case ' ':
10414                   case '\t':
10415                   case '\n':
10416                   case '\r':
10417                     break;
10418                   default:
10419                     initial_position[i][j++] = CharToPiece(*p);
10420                     break;
10421                 }
10422             while (*p == ' ' || *p == '\t' ||
10423                    *p == '\n' || *p == '\r') p++;
10424
10425             if (strncmp(p, "black", strlen("black"))==0)
10426               blackPlaysFirst = TRUE;
10427             else
10428               blackPlaysFirst = FALSE;
10429             startedFromSetupPosition = TRUE;
10430
10431             CopyBoard(boards[0], initial_position);
10432             if (blackPlaysFirst) {
10433                 currentMove = forwardMostMove = backwardMostMove = 1;
10434                 CopyBoard(boards[1], initial_position);
10435                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10436                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10437                 timeRemaining[0][1] = whiteTimeRemaining;
10438                 timeRemaining[1][1] = blackTimeRemaining;
10439                 if (commentList[0] != NULL) {
10440                     commentList[1] = commentList[0];
10441                     commentList[0] = NULL;
10442                 }
10443             } else {
10444                 currentMove = forwardMostMove = backwardMostMove = 0;
10445             }
10446         }
10447         yyboardindex = forwardMostMove;
10448         cm = (ChessMove) Myylex();
10449     }
10450
10451     if (first.pr == NoProc) {
10452         StartChessProgram(&first);
10453     }
10454     InitChessProgram(&first, FALSE);
10455     SendToProgram("force\n", &first);
10456     if (startedFromSetupPosition) {
10457         SendBoard(&first, forwardMostMove);
10458     if (appData.debugMode) {
10459         fprintf(debugFP, "Load Game\n");
10460     }
10461         DisplayBothClocks();
10462     }
10463
10464     /* [HGM] server: flag to write setup moves in broadcast file as one */
10465     loadFlag = appData.suppressLoadMoves;
10466
10467     while (cm == Comment) {
10468         char *p;
10469         if (appData.debugMode)
10470           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10471         p = yy_text;
10472         AppendComment(currentMove, p, FALSE);
10473         yyboardindex = forwardMostMove;
10474         cm = (ChessMove) Myylex();
10475     }
10476
10477     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10478         cm == WhiteWins || cm == BlackWins ||
10479         cm == GameIsDrawn || cm == GameUnfinished) {
10480         DisplayMessage("", _("No moves in game"));
10481         if (cmailMsgLoaded) {
10482             if (appData.debugMode)
10483               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10484             ClearHighlights();
10485             flipView = FALSE;
10486         }
10487         DrawPosition(FALSE, boards[currentMove]);
10488         DisplayBothClocks();
10489         gameMode = EditGame;
10490         ModeHighlight();
10491         gameFileFP = NULL;
10492         cmailOldMove = 0;
10493         return TRUE;
10494     }
10495
10496     // [HGM] PV info: routine tests if comment empty
10497     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10498         DisplayComment(currentMove - 1, commentList[currentMove]);
10499     }
10500     if (!matchMode && appData.timeDelay != 0)
10501       DrawPosition(FALSE, boards[currentMove]);
10502
10503     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10504       programStats.ok_to_send = 1;
10505     }
10506
10507     /* if the first token after the PGN tags is a move
10508      * and not move number 1, retrieve it from the parser
10509      */
10510     if (cm != MoveNumberOne)
10511         LoadGameOneMove(cm);
10512
10513     /* load the remaining moves from the file */
10514     while (LoadGameOneMove(EndOfFile)) {
10515       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10516       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10517     }
10518
10519     /* rewind to the start of the game */
10520     currentMove = backwardMostMove;
10521
10522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10523
10524     if (oldGameMode == AnalyzeFile ||
10525         oldGameMode == AnalyzeMode) {
10526       AnalyzeFileEvent();
10527     }
10528
10529     if (matchMode || appData.timeDelay == 0) {
10530       ToEndEvent();
10531       gameMode = EditGame;
10532       ModeHighlight();
10533     } else if (appData.timeDelay > 0) {
10534       AutoPlayGameLoop();
10535     }
10536
10537     if (appData.debugMode)
10538         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10539
10540     loadFlag = 0; /* [HGM] true game starts */
10541     return TRUE;
10542 }
10543
10544 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10545 int
10546 ReloadPosition(offset)
10547      int offset;
10548 {
10549     int positionNumber = lastLoadPositionNumber + offset;
10550     if (lastLoadPositionFP == NULL) {
10551         DisplayError(_("No position has been loaded yet"), 0);
10552         return FALSE;
10553     }
10554     if (positionNumber <= 0) {
10555         DisplayError(_("Can't back up any further"), 0);
10556         return FALSE;
10557     }
10558     return LoadPosition(lastLoadPositionFP, positionNumber,
10559                         lastLoadPositionTitle);
10560 }
10561
10562 /* Load the nth position from the given file */
10563 int
10564 LoadPositionFromFile(filename, n, title)
10565      char *filename;
10566      int n;
10567      char *title;
10568 {
10569     FILE *f;
10570     char buf[MSG_SIZ];
10571
10572     if (strcmp(filename, "-") == 0) {
10573         return LoadPosition(stdin, n, "stdin");
10574     } else {
10575         f = fopen(filename, "rb");
10576         if (f == NULL) {
10577             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10578             DisplayError(buf, errno);
10579             return FALSE;
10580         } else {
10581             return LoadPosition(f, n, title);
10582         }
10583     }
10584 }
10585
10586 /* Load the nth position from the given open file, and close it */
10587 int
10588 LoadPosition(f, positionNumber, title)
10589      FILE *f;
10590      int positionNumber;
10591      char *title;
10592 {
10593     char *p, line[MSG_SIZ];
10594     Board initial_position;
10595     int i, j, fenMode, pn;
10596
10597     if (gameMode == Training )
10598         SetTrainingModeOff();
10599
10600     if (gameMode != BeginningOfGame) {
10601         Reset(FALSE, TRUE);
10602     }
10603     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10604         fclose(lastLoadPositionFP);
10605     }
10606     if (positionNumber == 0) positionNumber = 1;
10607     lastLoadPositionFP = f;
10608     lastLoadPositionNumber = positionNumber;
10609     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10610     if (first.pr == NoProc) {
10611       StartChessProgram(&first);
10612       InitChessProgram(&first, FALSE);
10613     }
10614     pn = positionNumber;
10615     if (positionNumber < 0) {
10616         /* Negative position number means to seek to that byte offset */
10617         if (fseek(f, -positionNumber, 0) == -1) {
10618             DisplayError(_("Can't seek on position file"), 0);
10619             return FALSE;
10620         };
10621         pn = 1;
10622     } else {
10623         if (fseek(f, 0, 0) == -1) {
10624             if (f == lastLoadPositionFP ?
10625                 positionNumber == lastLoadPositionNumber + 1 :
10626                 positionNumber == 1) {
10627                 pn = 1;
10628             } else {
10629                 DisplayError(_("Can't seek on position file"), 0);
10630                 return FALSE;
10631             }
10632         }
10633     }
10634     /* See if this file is FEN or old-style xboard */
10635     if (fgets(line, MSG_SIZ, f) == NULL) {
10636         DisplayError(_("Position not found in file"), 0);
10637         return FALSE;
10638     }
10639     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10640     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10641
10642     if (pn >= 2) {
10643         if (fenMode || line[0] == '#') pn--;
10644         while (pn > 0) {
10645             /* skip positions before number pn */
10646             if (fgets(line, MSG_SIZ, f) == NULL) {
10647                 Reset(TRUE, TRUE);
10648                 DisplayError(_("Position not found in file"), 0);
10649                 return FALSE;
10650             }
10651             if (fenMode || line[0] == '#') pn--;
10652         }
10653     }
10654
10655     if (fenMode) {
10656         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10657             DisplayError(_("Bad FEN position in file"), 0);
10658             return FALSE;
10659         }
10660     } else {
10661         (void) fgets(line, MSG_SIZ, f);
10662         (void) fgets(line, MSG_SIZ, f);
10663
10664         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10665             (void) fgets(line, MSG_SIZ, f);
10666             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10667                 if (*p == ' ')
10668                   continue;
10669                 initial_position[i][j++] = CharToPiece(*p);
10670             }
10671         }
10672
10673         blackPlaysFirst = FALSE;
10674         if (!feof(f)) {
10675             (void) fgets(line, MSG_SIZ, f);
10676             if (strncmp(line, "black", strlen("black"))==0)
10677               blackPlaysFirst = TRUE;
10678         }
10679     }
10680     startedFromSetupPosition = TRUE;
10681
10682     SendToProgram("force\n", &first);
10683     CopyBoard(boards[0], initial_position);
10684     if (blackPlaysFirst) {
10685         currentMove = forwardMostMove = backwardMostMove = 1;
10686         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10687         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10688         CopyBoard(boards[1], initial_position);
10689         DisplayMessage("", _("Black to play"));
10690     } else {
10691         currentMove = forwardMostMove = backwardMostMove = 0;
10692         DisplayMessage("", _("White to play"));
10693     }
10694     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10695     SendBoard(&first, forwardMostMove);
10696     if (appData.debugMode) {
10697 int i, j;
10698   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10699   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10700         fprintf(debugFP, "Load Position\n");
10701     }
10702
10703     if (positionNumber > 1) {
10704       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10705         DisplayTitle(line);
10706     } else {
10707         DisplayTitle(title);
10708     }
10709     gameMode = EditGame;
10710     ModeHighlight();
10711     ResetClocks();
10712     timeRemaining[0][1] = whiteTimeRemaining;
10713     timeRemaining[1][1] = blackTimeRemaining;
10714     DrawPosition(FALSE, boards[currentMove]);
10715
10716     return TRUE;
10717 }
10718
10719
10720 void
10721 CopyPlayerNameIntoFileName(dest, src)
10722      char **dest, *src;
10723 {
10724     while (*src != NULLCHAR && *src != ',') {
10725         if (*src == ' ') {
10726             *(*dest)++ = '_';
10727             src++;
10728         } else {
10729             *(*dest)++ = *src++;
10730         }
10731     }
10732 }
10733
10734 char *DefaultFileName(ext)
10735      char *ext;
10736 {
10737     static char def[MSG_SIZ];
10738     char *p;
10739
10740     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10741         p = def;
10742         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10743         *p++ = '-';
10744         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10745         *p++ = '.';
10746         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10747     } else {
10748         def[0] = NULLCHAR;
10749     }
10750     return def;
10751 }
10752
10753 /* Save the current game to the given file */
10754 int
10755 SaveGameToFile(filename, append)
10756      char *filename;
10757      int append;
10758 {
10759     FILE *f;
10760     char buf[MSG_SIZ];
10761
10762     if (strcmp(filename, "-") == 0) {
10763         return SaveGame(stdout, 0, NULL);
10764     } else {
10765         f = fopen(filename, append ? "a" : "w");
10766         if (f == NULL) {
10767             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10768             DisplayError(buf, errno);
10769             return FALSE;
10770         } else {
10771             return SaveGame(f, 0, NULL);
10772         }
10773     }
10774 }
10775
10776 char *
10777 SavePart(str)
10778      char *str;
10779 {
10780     static char buf[MSG_SIZ];
10781     char *p;
10782
10783     p = strchr(str, ' ');
10784     if (p == NULL) return str;
10785     strncpy(buf, str, p - str);
10786     buf[p - str] = NULLCHAR;
10787     return buf;
10788 }
10789
10790 #define PGN_MAX_LINE 75
10791
10792 #define PGN_SIDE_WHITE  0
10793 #define PGN_SIDE_BLACK  1
10794
10795 /* [AS] */
10796 static int FindFirstMoveOutOfBook( int side )
10797 {
10798     int result = -1;
10799
10800     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10801         int index = backwardMostMove;
10802         int has_book_hit = 0;
10803
10804         if( (index % 2) != side ) {
10805             index++;
10806         }
10807
10808         while( index < forwardMostMove ) {
10809             /* Check to see if engine is in book */
10810             int depth = pvInfoList[index].depth;
10811             int score = pvInfoList[index].score;
10812             int in_book = 0;
10813
10814             if( depth <= 2 ) {
10815                 in_book = 1;
10816             }
10817             else if( score == 0 && depth == 63 ) {
10818                 in_book = 1; /* Zappa */
10819             }
10820             else if( score == 2 && depth == 99 ) {
10821                 in_book = 1; /* Abrok */
10822             }
10823
10824             has_book_hit += in_book;
10825
10826             if( ! in_book ) {
10827                 result = index;
10828
10829                 break;
10830             }
10831
10832             index += 2;
10833         }
10834     }
10835
10836     return result;
10837 }
10838
10839 /* [AS] */
10840 void GetOutOfBookInfo( char * buf )
10841 {
10842     int oob[2];
10843     int i;
10844     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10845
10846     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10847     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10848
10849     *buf = '\0';
10850
10851     if( oob[0] >= 0 || oob[1] >= 0 ) {
10852         for( i=0; i<2; i++ ) {
10853             int idx = oob[i];
10854
10855             if( idx >= 0 ) {
10856                 if( i > 0 && oob[0] >= 0 ) {
10857                     strcat( buf, "   " );
10858                 }
10859
10860                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10861                 sprintf( buf+strlen(buf), "%s%.2f",
10862                     pvInfoList[idx].score >= 0 ? "+" : "",
10863                     pvInfoList[idx].score / 100.0 );
10864             }
10865         }
10866     }
10867 }
10868
10869 /* Save game in PGN style and close the file */
10870 int
10871 SaveGamePGN(f)
10872      FILE *f;
10873 {
10874     int i, offset, linelen, newblock;
10875     time_t tm;
10876 //    char *movetext;
10877     char numtext[32];
10878     int movelen, numlen, blank;
10879     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10880
10881     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10882
10883     tm = time((time_t *) NULL);
10884
10885     PrintPGNTags(f, &gameInfo);
10886
10887     if (backwardMostMove > 0 || startedFromSetupPosition) {
10888         char *fen = PositionToFEN(backwardMostMove, NULL);
10889         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10890         fprintf(f, "\n{--------------\n");
10891         PrintPosition(f, backwardMostMove);
10892         fprintf(f, "--------------}\n");
10893         free(fen);
10894     }
10895     else {
10896         /* [AS] Out of book annotation */
10897         if( appData.saveOutOfBookInfo ) {
10898             char buf[64];
10899
10900             GetOutOfBookInfo( buf );
10901
10902             if( buf[0] != '\0' ) {
10903                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10904             }
10905         }
10906
10907         fprintf(f, "\n");
10908     }
10909
10910     i = backwardMostMove;
10911     linelen = 0;
10912     newblock = TRUE;
10913
10914     while (i < forwardMostMove) {
10915         /* Print comments preceding this move */
10916         if (commentList[i] != NULL) {
10917             if (linelen > 0) fprintf(f, "\n");
10918             fprintf(f, "%s", commentList[i]);
10919             linelen = 0;
10920             newblock = TRUE;
10921         }
10922
10923         /* Format move number */
10924         if ((i % 2) == 0)
10925           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10926         else
10927           if (newblock)
10928             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10929           else
10930             numtext[0] = NULLCHAR;
10931
10932         numlen = strlen(numtext);
10933         newblock = FALSE;
10934
10935         /* Print move number */
10936         blank = linelen > 0 && numlen > 0;
10937         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10938             fprintf(f, "\n");
10939             linelen = 0;
10940             blank = 0;
10941         }
10942         if (blank) {
10943             fprintf(f, " ");
10944             linelen++;
10945         }
10946         fprintf(f, "%s", numtext);
10947         linelen += numlen;
10948
10949         /* Get move */
10950         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10951         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10952
10953         /* Print move */
10954         blank = linelen > 0 && movelen > 0;
10955         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10956             fprintf(f, "\n");
10957             linelen = 0;
10958             blank = 0;
10959         }
10960         if (blank) {
10961             fprintf(f, " ");
10962             linelen++;
10963         }
10964         fprintf(f, "%s", move_buffer);
10965         linelen += movelen;
10966
10967         /* [AS] Add PV info if present */
10968         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10969             /* [HGM] add time */
10970             char buf[MSG_SIZ]; int seconds;
10971
10972             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10973
10974             if( seconds <= 0)
10975               buf[0] = 0;
10976             else
10977               if( seconds < 30 )
10978                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10979               else
10980                 {
10981                   seconds = (seconds + 4)/10; // round to full seconds
10982                   if( seconds < 60 )
10983                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10984                   else
10985                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10986                 }
10987
10988             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10989                       pvInfoList[i].score >= 0 ? "+" : "",
10990                       pvInfoList[i].score / 100.0,
10991                       pvInfoList[i].depth,
10992                       buf );
10993
10994             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10995
10996             /* Print score/depth */
10997             blank = linelen > 0 && movelen > 0;
10998             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10999                 fprintf(f, "\n");
11000                 linelen = 0;
11001                 blank = 0;
11002             }
11003             if (blank) {
11004                 fprintf(f, " ");
11005                 linelen++;
11006             }
11007             fprintf(f, "%s", move_buffer);
11008             linelen += movelen;
11009         }
11010
11011         i++;
11012     }
11013
11014     /* Start a new line */
11015     if (linelen > 0) fprintf(f, "\n");
11016
11017     /* Print comments after last move */
11018     if (commentList[i] != NULL) {
11019         fprintf(f, "%s\n", commentList[i]);
11020     }
11021
11022     /* Print result */
11023     if (gameInfo.resultDetails != NULL &&
11024         gameInfo.resultDetails[0] != NULLCHAR) {
11025         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11026                 PGNResult(gameInfo.result));
11027     } else {
11028         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11029     }
11030
11031     fclose(f);
11032     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11033     return TRUE;
11034 }
11035
11036 /* Save game in old style and close the file */
11037 int
11038 SaveGameOldStyle(f)
11039      FILE *f;
11040 {
11041     int i, offset;
11042     time_t tm;
11043
11044     tm = time((time_t *) NULL);
11045
11046     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11047     PrintOpponents(f);
11048
11049     if (backwardMostMove > 0 || startedFromSetupPosition) {
11050         fprintf(f, "\n[--------------\n");
11051         PrintPosition(f, backwardMostMove);
11052         fprintf(f, "--------------]\n");
11053     } else {
11054         fprintf(f, "\n");
11055     }
11056
11057     i = backwardMostMove;
11058     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11059
11060     while (i < forwardMostMove) {
11061         if (commentList[i] != NULL) {
11062             fprintf(f, "[%s]\n", commentList[i]);
11063         }
11064
11065         if ((i % 2) == 1) {
11066             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11067             i++;
11068         } else {
11069             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11070             i++;
11071             if (commentList[i] != NULL) {
11072                 fprintf(f, "\n");
11073                 continue;
11074             }
11075             if (i >= forwardMostMove) {
11076                 fprintf(f, "\n");
11077                 break;
11078             }
11079             fprintf(f, "%s\n", parseList[i]);
11080             i++;
11081         }
11082     }
11083
11084     if (commentList[i] != NULL) {
11085         fprintf(f, "[%s]\n", commentList[i]);
11086     }
11087
11088     /* This isn't really the old style, but it's close enough */
11089     if (gameInfo.resultDetails != NULL &&
11090         gameInfo.resultDetails[0] != NULLCHAR) {
11091         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11092                 gameInfo.resultDetails);
11093     } else {
11094         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11095     }
11096
11097     fclose(f);
11098     return TRUE;
11099 }
11100
11101 /* Save the current game to open file f and close the file */
11102 int
11103 SaveGame(f, dummy, dummy2)
11104      FILE *f;
11105      int dummy;
11106      char *dummy2;
11107 {
11108     if (gameMode == EditPosition) EditPositionDone(TRUE);
11109     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11110     if (appData.oldSaveStyle)
11111       return SaveGameOldStyle(f);
11112     else
11113       return SaveGamePGN(f);
11114 }
11115
11116 /* Save the current position to the given file */
11117 int
11118 SavePositionToFile(filename)
11119      char *filename;
11120 {
11121     FILE *f;
11122     char buf[MSG_SIZ];
11123
11124     if (strcmp(filename, "-") == 0) {
11125         return SavePosition(stdout, 0, NULL);
11126     } else {
11127         f = fopen(filename, "a");
11128         if (f == NULL) {
11129             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11130             DisplayError(buf, errno);
11131             return FALSE;
11132         } else {
11133             SavePosition(f, 0, NULL);
11134             return TRUE;
11135         }
11136     }
11137 }
11138
11139 /* Save the current position to the given open file and close the file */
11140 int
11141 SavePosition(f, dummy, dummy2)
11142      FILE *f;
11143      int dummy;
11144      char *dummy2;
11145 {
11146     time_t tm;
11147     char *fen;
11148
11149     if (gameMode == EditPosition) EditPositionDone(TRUE);
11150     if (appData.oldSaveStyle) {
11151         tm = time((time_t *) NULL);
11152
11153         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11154         PrintOpponents(f);
11155         fprintf(f, "[--------------\n");
11156         PrintPosition(f, currentMove);
11157         fprintf(f, "--------------]\n");
11158     } else {
11159         fen = PositionToFEN(currentMove, NULL);
11160         fprintf(f, "%s\n", fen);
11161         free(fen);
11162     }
11163     fclose(f);
11164     return TRUE;
11165 }
11166
11167 void
11168 ReloadCmailMsgEvent(unregister)
11169      int unregister;
11170 {
11171 #if !WIN32
11172     static char *inFilename = NULL;
11173     static char *outFilename;
11174     int i;
11175     struct stat inbuf, outbuf;
11176     int status;
11177
11178     /* Any registered moves are unregistered if unregister is set, */
11179     /* i.e. invoked by the signal handler */
11180     if (unregister) {
11181         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11182             cmailMoveRegistered[i] = FALSE;
11183             if (cmailCommentList[i] != NULL) {
11184                 free(cmailCommentList[i]);
11185                 cmailCommentList[i] = NULL;
11186             }
11187         }
11188         nCmailMovesRegistered = 0;
11189     }
11190
11191     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11192         cmailResult[i] = CMAIL_NOT_RESULT;
11193     }
11194     nCmailResults = 0;
11195
11196     if (inFilename == NULL) {
11197         /* Because the filenames are static they only get malloced once  */
11198         /* and they never get freed                                      */
11199         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11200         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11201
11202         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11203         sprintf(outFilename, "%s.out", appData.cmailGameName);
11204     }
11205
11206     status = stat(outFilename, &outbuf);
11207     if (status < 0) {
11208         cmailMailedMove = FALSE;
11209     } else {
11210         status = stat(inFilename, &inbuf);
11211         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11212     }
11213
11214     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11215        counts the games, notes how each one terminated, etc.
11216
11217        It would be nice to remove this kludge and instead gather all
11218        the information while building the game list.  (And to keep it
11219        in the game list nodes instead of having a bunch of fixed-size
11220        parallel arrays.)  Note this will require getting each game's
11221        termination from the PGN tags, as the game list builder does
11222        not process the game moves.  --mann
11223        */
11224     cmailMsgLoaded = TRUE;
11225     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11226
11227     /* Load first game in the file or popup game menu */
11228     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11229
11230 #endif /* !WIN32 */
11231     return;
11232 }
11233
11234 int
11235 RegisterMove()
11236 {
11237     FILE *f;
11238     char string[MSG_SIZ];
11239
11240     if (   cmailMailedMove
11241         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11242         return TRUE;            /* Allow free viewing  */
11243     }
11244
11245     /* Unregister move to ensure that we don't leave RegisterMove        */
11246     /* with the move registered when the conditions for registering no   */
11247     /* longer hold                                                       */
11248     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11249         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11250         nCmailMovesRegistered --;
11251
11252         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11253           {
11254               free(cmailCommentList[lastLoadGameNumber - 1]);
11255               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11256           }
11257     }
11258
11259     if (cmailOldMove == -1) {
11260         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11261         return FALSE;
11262     }
11263
11264     if (currentMove > cmailOldMove + 1) {
11265         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11266         return FALSE;
11267     }
11268
11269     if (currentMove < cmailOldMove) {
11270         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11271         return FALSE;
11272     }
11273
11274     if (forwardMostMove > currentMove) {
11275         /* Silently truncate extra moves */
11276         TruncateGame();
11277     }
11278
11279     if (   (currentMove == cmailOldMove + 1)
11280         || (   (currentMove == cmailOldMove)
11281             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11282                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11283         if (gameInfo.result != GameUnfinished) {
11284             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11285         }
11286
11287         if (commentList[currentMove] != NULL) {
11288             cmailCommentList[lastLoadGameNumber - 1]
11289               = StrSave(commentList[currentMove]);
11290         }
11291         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11292
11293         if (appData.debugMode)
11294           fprintf(debugFP, "Saving %s for game %d\n",
11295                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11296
11297         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11298
11299         f = fopen(string, "w");
11300         if (appData.oldSaveStyle) {
11301             SaveGameOldStyle(f); /* also closes the file */
11302
11303             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11304             f = fopen(string, "w");
11305             SavePosition(f, 0, NULL); /* also closes the file */
11306         } else {
11307             fprintf(f, "{--------------\n");
11308             PrintPosition(f, currentMove);
11309             fprintf(f, "--------------}\n\n");
11310
11311             SaveGame(f, 0, NULL); /* also closes the file*/
11312         }
11313
11314         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11315         nCmailMovesRegistered ++;
11316     } else if (nCmailGames == 1) {
11317         DisplayError(_("You have not made a move yet"), 0);
11318         return FALSE;
11319     }
11320
11321     return TRUE;
11322 }
11323
11324 void
11325 MailMoveEvent()
11326 {
11327 #if !WIN32
11328     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11329     FILE *commandOutput;
11330     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11331     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11332     int nBuffers;
11333     int i;
11334     int archived;
11335     char *arcDir;
11336
11337     if (! cmailMsgLoaded) {
11338         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11339         return;
11340     }
11341
11342     if (nCmailGames == nCmailResults) {
11343         DisplayError(_("No unfinished games"), 0);
11344         return;
11345     }
11346
11347 #if CMAIL_PROHIBIT_REMAIL
11348     if (cmailMailedMove) {
11349       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);
11350         DisplayError(msg, 0);
11351         return;
11352     }
11353 #endif
11354
11355     if (! (cmailMailedMove || RegisterMove())) return;
11356
11357     if (   cmailMailedMove
11358         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11359       snprintf(string, MSG_SIZ, partCommandString,
11360                appData.debugMode ? " -v" : "", appData.cmailGameName);
11361         commandOutput = popen(string, "r");
11362
11363         if (commandOutput == NULL) {
11364             DisplayError(_("Failed to invoke cmail"), 0);
11365         } else {
11366             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11367                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11368             }
11369             if (nBuffers > 1) {
11370                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11371                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11372                 nBytes = MSG_SIZ - 1;
11373             } else {
11374                 (void) memcpy(msg, buffer, nBytes);
11375             }
11376             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11377
11378             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11379                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11380
11381                 archived = TRUE;
11382                 for (i = 0; i < nCmailGames; i ++) {
11383                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11384                         archived = FALSE;
11385                     }
11386                 }
11387                 if (   archived
11388                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11389                         != NULL)) {
11390                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11391                            arcDir,
11392                            appData.cmailGameName,
11393                            gameInfo.date);
11394                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11395                     cmailMsgLoaded = FALSE;
11396                 }
11397             }
11398
11399             DisplayInformation(msg);
11400             pclose(commandOutput);
11401         }
11402     } else {
11403         if ((*cmailMsg) != '\0') {
11404             DisplayInformation(cmailMsg);
11405         }
11406     }
11407
11408     return;
11409 #endif /* !WIN32 */
11410 }
11411
11412 char *
11413 CmailMsg()
11414 {
11415 #if WIN32
11416     return NULL;
11417 #else
11418     int  prependComma = 0;
11419     char number[5];
11420     char string[MSG_SIZ];       /* Space for game-list */
11421     int  i;
11422
11423     if (!cmailMsgLoaded) return "";
11424
11425     if (cmailMailedMove) {
11426       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11427     } else {
11428         /* Create a list of games left */
11429       snprintf(string, MSG_SIZ, "[");
11430         for (i = 0; i < nCmailGames; i ++) {
11431             if (! (   cmailMoveRegistered[i]
11432                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11433                 if (prependComma) {
11434                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11435                 } else {
11436                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11437                     prependComma = 1;
11438                 }
11439
11440                 strcat(string, number);
11441             }
11442         }
11443         strcat(string, "]");
11444
11445         if (nCmailMovesRegistered + nCmailResults == 0) {
11446             switch (nCmailGames) {
11447               case 1:
11448                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11449                 break;
11450
11451               case 2:
11452                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11453                 break;
11454
11455               default:
11456                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11457                          nCmailGames);
11458                 break;
11459             }
11460         } else {
11461             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11462               case 1:
11463                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11464                          string);
11465                 break;
11466
11467               case 0:
11468                 if (nCmailResults == nCmailGames) {
11469                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11470                 } else {
11471                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11472                 }
11473                 break;
11474
11475               default:
11476                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11477                          string);
11478             }
11479         }
11480     }
11481     return cmailMsg;
11482 #endif /* WIN32 */
11483 }
11484
11485 void
11486 ResetGameEvent()
11487 {
11488     if (gameMode == Training)
11489       SetTrainingModeOff();
11490
11491     Reset(TRUE, TRUE);
11492     cmailMsgLoaded = FALSE;
11493     if (appData.icsActive) {
11494       SendToICS(ics_prefix);
11495       SendToICS("refresh\n");
11496     }
11497 }
11498
11499 void
11500 ExitEvent(status)
11501      int status;
11502 {
11503     exiting++;
11504     if (exiting > 2) {
11505       /* Give up on clean exit */
11506       exit(status);
11507     }
11508     if (exiting > 1) {
11509       /* Keep trying for clean exit */
11510       return;
11511     }
11512
11513     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11514
11515     if (telnetISR != NULL) {
11516       RemoveInputSource(telnetISR);
11517     }
11518     if (icsPR != NoProc) {
11519       DestroyChildProcess(icsPR, TRUE);
11520     }
11521
11522     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11523     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11524
11525     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11526     /* make sure this other one finishes before killing it!                  */
11527     if(endingGame) { int count = 0;
11528         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11529         while(endingGame && count++ < 10) DoSleep(1);
11530         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11531     }
11532
11533     /* Kill off chess programs */
11534     if (first.pr != NoProc) {
11535         ExitAnalyzeMode();
11536
11537         DoSleep( appData.delayBeforeQuit );
11538         SendToProgram("quit\n", &first);
11539         DoSleep( appData.delayAfterQuit );
11540         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11541     }
11542     if (second.pr != NoProc) {
11543         DoSleep( appData.delayBeforeQuit );
11544         SendToProgram("quit\n", &second);
11545         DoSleep( appData.delayAfterQuit );
11546         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11547     }
11548     if (first.isr != NULL) {
11549         RemoveInputSource(first.isr);
11550     }
11551     if (second.isr != NULL) {
11552         RemoveInputSource(second.isr);
11553     }
11554
11555     ShutDownFrontEnd();
11556     exit(status);
11557 }
11558
11559 void
11560 PauseEvent()
11561 {
11562     if (appData.debugMode)
11563         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11564     if (pausing) {
11565         pausing = FALSE;
11566         ModeHighlight();
11567         if (gameMode == MachinePlaysWhite ||
11568             gameMode == MachinePlaysBlack) {
11569             StartClocks();
11570         } else {
11571             DisplayBothClocks();
11572         }
11573         if (gameMode == PlayFromGameFile) {
11574             if (appData.timeDelay >= 0)
11575                 AutoPlayGameLoop();
11576         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11577             Reset(FALSE, TRUE);
11578             SendToICS(ics_prefix);
11579             SendToICS("refresh\n");
11580         } else if (currentMove < forwardMostMove) {
11581             ForwardInner(forwardMostMove);
11582         }
11583         pauseExamInvalid = FALSE;
11584     } else {
11585         switch (gameMode) {
11586           default:
11587             return;
11588           case IcsExamining:
11589             pauseExamForwardMostMove = forwardMostMove;
11590             pauseExamInvalid = FALSE;
11591             /* fall through */
11592           case IcsObserving:
11593           case IcsPlayingWhite:
11594           case IcsPlayingBlack:
11595             pausing = TRUE;
11596             ModeHighlight();
11597             return;
11598           case PlayFromGameFile:
11599             (void) StopLoadGameTimer();
11600             pausing = TRUE;
11601             ModeHighlight();
11602             break;
11603           case BeginningOfGame:
11604             if (appData.icsActive) return;
11605             /* else fall through */
11606           case MachinePlaysWhite:
11607           case MachinePlaysBlack:
11608           case TwoMachinesPlay:
11609             if (forwardMostMove == 0)
11610               return;           /* don't pause if no one has moved */
11611             if ((gameMode == MachinePlaysWhite &&
11612                  !WhiteOnMove(forwardMostMove)) ||
11613                 (gameMode == MachinePlaysBlack &&
11614                  WhiteOnMove(forwardMostMove))) {
11615                 StopClocks();
11616             }
11617             pausing = TRUE;
11618             ModeHighlight();
11619             break;
11620         }
11621     }
11622 }
11623
11624 void
11625 EditCommentEvent()
11626 {
11627     char title[MSG_SIZ];
11628
11629     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11630       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11631     } else {
11632       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11633                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11634                parseList[currentMove - 1]);
11635     }
11636
11637     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11638 }
11639
11640
11641 void
11642 EditTagsEvent()
11643 {
11644     char *tags = PGNTags(&gameInfo);
11645     EditTagsPopUp(tags);
11646     free(tags);
11647 }
11648
11649 void
11650 AnalyzeModeEvent()
11651 {
11652     if (appData.noChessProgram || gameMode == AnalyzeMode)
11653       return;
11654
11655     if (gameMode != AnalyzeFile) {
11656         if (!appData.icsEngineAnalyze) {
11657                EditGameEvent();
11658                if (gameMode != EditGame) return;
11659         }
11660         ResurrectChessProgram();
11661         SendToProgram("analyze\n", &first);
11662         first.analyzing = TRUE;
11663         /*first.maybeThinking = TRUE;*/
11664         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11665         EngineOutputPopUp();
11666     }
11667     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11668     pausing = FALSE;
11669     ModeHighlight();
11670     SetGameInfo();
11671
11672     StartAnalysisClock();
11673     GetTimeMark(&lastNodeCountTime);
11674     lastNodeCount = 0;
11675 }
11676
11677 void
11678 AnalyzeFileEvent()
11679 {
11680     if (appData.noChessProgram || gameMode == AnalyzeFile)
11681       return;
11682
11683     if (gameMode != AnalyzeMode) {
11684         EditGameEvent();
11685         if (gameMode != EditGame) return;
11686         ResurrectChessProgram();
11687         SendToProgram("analyze\n", &first);
11688         first.analyzing = TRUE;
11689         /*first.maybeThinking = TRUE;*/
11690         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11691         EngineOutputPopUp();
11692     }
11693     gameMode = AnalyzeFile;
11694     pausing = FALSE;
11695     ModeHighlight();
11696     SetGameInfo();
11697
11698     StartAnalysisClock();
11699     GetTimeMark(&lastNodeCountTime);
11700     lastNodeCount = 0;
11701 }
11702
11703 void
11704 MachineWhiteEvent()
11705 {
11706     char buf[MSG_SIZ];
11707     char *bookHit = NULL;
11708
11709     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11710       return;
11711
11712
11713     if (gameMode == PlayFromGameFile ||
11714         gameMode == TwoMachinesPlay  ||
11715         gameMode == Training         ||
11716         gameMode == AnalyzeMode      ||
11717         gameMode == EndOfGame)
11718         EditGameEvent();
11719
11720     if (gameMode == EditPosition)
11721         EditPositionDone(TRUE);
11722
11723     if (!WhiteOnMove(currentMove)) {
11724         DisplayError(_("It is not White's turn"), 0);
11725         return;
11726     }
11727
11728     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11729       ExitAnalyzeMode();
11730
11731     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11732         gameMode == AnalyzeFile)
11733         TruncateGame();
11734
11735     ResurrectChessProgram();    /* in case it isn't running */
11736     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11737         gameMode = MachinePlaysWhite;
11738         ResetClocks();
11739     } else
11740     gameMode = MachinePlaysWhite;
11741     pausing = FALSE;
11742     ModeHighlight();
11743     SetGameInfo();
11744     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11745     DisplayTitle(buf);
11746     if (first.sendName) {
11747       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11748       SendToProgram(buf, &first);
11749     }
11750     if (first.sendTime) {
11751       if (first.useColors) {
11752         SendToProgram("black\n", &first); /*gnu kludge*/
11753       }
11754       SendTimeRemaining(&first, TRUE);
11755     }
11756     if (first.useColors) {
11757       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11758     }
11759     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11760     SetMachineThinkingEnables();
11761     first.maybeThinking = TRUE;
11762     StartClocks();
11763     firstMove = FALSE;
11764
11765     if (appData.autoFlipView && !flipView) {
11766       flipView = !flipView;
11767       DrawPosition(FALSE, NULL);
11768       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11769     }
11770
11771     if(bookHit) { // [HGM] book: simulate book reply
11772         static char bookMove[MSG_SIZ]; // a bit generous?
11773
11774         programStats.nodes = programStats.depth = programStats.time =
11775         programStats.score = programStats.got_only_move = 0;
11776         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11777
11778         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11779         strcat(bookMove, bookHit);
11780         HandleMachineMove(bookMove, &first);
11781     }
11782 }
11783
11784 void
11785 MachineBlackEvent()
11786 {
11787   char buf[MSG_SIZ];
11788   char *bookHit = NULL;
11789
11790     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11791         return;
11792
11793
11794     if (gameMode == PlayFromGameFile ||
11795         gameMode == TwoMachinesPlay  ||
11796         gameMode == Training         ||
11797         gameMode == AnalyzeMode      ||
11798         gameMode == EndOfGame)
11799         EditGameEvent();
11800
11801     if (gameMode == EditPosition)
11802         EditPositionDone(TRUE);
11803
11804     if (WhiteOnMove(currentMove)) {
11805         DisplayError(_("It is not Black's turn"), 0);
11806         return;
11807     }
11808
11809     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11810       ExitAnalyzeMode();
11811
11812     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11813         gameMode == AnalyzeFile)
11814         TruncateGame();
11815
11816     ResurrectChessProgram();    /* in case it isn't running */
11817     gameMode = MachinePlaysBlack;
11818     pausing = FALSE;
11819     ModeHighlight();
11820     SetGameInfo();
11821     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11822     DisplayTitle(buf);
11823     if (first.sendName) {
11824       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11825       SendToProgram(buf, &first);
11826     }
11827     if (first.sendTime) {
11828       if (first.useColors) {
11829         SendToProgram("white\n", &first); /*gnu kludge*/
11830       }
11831       SendTimeRemaining(&first, FALSE);
11832     }
11833     if (first.useColors) {
11834       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11835     }
11836     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11837     SetMachineThinkingEnables();
11838     first.maybeThinking = TRUE;
11839     StartClocks();
11840
11841     if (appData.autoFlipView && flipView) {
11842       flipView = !flipView;
11843       DrawPosition(FALSE, NULL);
11844       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11845     }
11846     if(bookHit) { // [HGM] book: simulate book reply
11847         static char bookMove[MSG_SIZ]; // a bit generous?
11848
11849         programStats.nodes = programStats.depth = programStats.time =
11850         programStats.score = programStats.got_only_move = 0;
11851         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11852
11853         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11854         strcat(bookMove, bookHit);
11855         HandleMachineMove(bookMove, &first);
11856     }
11857 }
11858
11859
11860 void
11861 DisplayTwoMachinesTitle()
11862 {
11863     char buf[MSG_SIZ];
11864     if (appData.matchGames > 0) {
11865         if (first.twoMachinesColor[0] == 'w') {
11866           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11867                    gameInfo.white, gameInfo.black,
11868                    first.matchWins, second.matchWins,
11869                    matchGame - 1 - (first.matchWins + second.matchWins));
11870         } else {
11871           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11872                    gameInfo.white, gameInfo.black,
11873                    second.matchWins, first.matchWins,
11874                    matchGame - 1 - (first.matchWins + second.matchWins));
11875         }
11876     } else {
11877       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11878     }
11879     DisplayTitle(buf);
11880 }
11881
11882 void
11883 SettingsMenuIfReady()
11884 {
11885   if (second.lastPing != second.lastPong) {
11886     DisplayMessage("", _("Waiting for second chess program"));
11887     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11888     return;
11889   }
11890   ThawUI();
11891   DisplayMessage("", "");
11892   SettingsPopUp(&second);
11893 }
11894
11895 int
11896 WaitForSecond(DelayedEventCallback retry)
11897 {
11898     if (second.pr == NULL) {
11899         StartChessProgram(&second);
11900         if (second.protocolVersion == 1) {
11901           retry();
11902         } else {
11903           /* kludge: allow timeout for initial "feature" command */
11904           FreezeUI();
11905           DisplayMessage("", _("Starting second chess program"));
11906           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11907         }
11908         return 1;
11909     }
11910     return 0;
11911 }
11912
11913 void
11914 TwoMachinesEvent P((void))
11915 {
11916     int i;
11917     char buf[MSG_SIZ];
11918     ChessProgramState *onmove;
11919     char *bookHit = NULL;
11920
11921     if (appData.noChessProgram) return;
11922
11923     switch (gameMode) {
11924       case TwoMachinesPlay:
11925         return;
11926       case MachinePlaysWhite:
11927       case MachinePlaysBlack:
11928         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11929             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11930             return;
11931         }
11932         /* fall through */
11933       case BeginningOfGame:
11934       case PlayFromGameFile:
11935       case EndOfGame:
11936         EditGameEvent();
11937         if (gameMode != EditGame) return;
11938         break;
11939       case EditPosition:
11940         EditPositionDone(TRUE);
11941         break;
11942       case AnalyzeMode:
11943       case AnalyzeFile:
11944         ExitAnalyzeMode();
11945         break;
11946       case EditGame:
11947       default:
11948         break;
11949     }
11950
11951 //    forwardMostMove = currentMove;
11952     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11953     ResurrectChessProgram();    /* in case first program isn't running */
11954
11955     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11956     DisplayMessage("", "");
11957     InitChessProgram(&second, FALSE);
11958     SendToProgram("force\n", &second);
11959     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11960       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11961       return;
11962     }
11963     if (startedFromSetupPosition) {
11964         SendBoard(&second, backwardMostMove);
11965     if (appData.debugMode) {
11966         fprintf(debugFP, "Two Machines\n");
11967     }
11968     }
11969     for (i = backwardMostMove; i < forwardMostMove; i++) {
11970         SendMoveToProgram(i, &second);
11971     }
11972
11973     gameMode = TwoMachinesPlay;
11974     pausing = FALSE;
11975     ModeHighlight();
11976     SetGameInfo();
11977     DisplayTwoMachinesTitle();
11978     firstMove = TRUE;
11979     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11980         onmove = &first;
11981     } else {
11982         onmove = &second;
11983     }
11984
11985     SendToProgram(first.computerString, &first);
11986     if (first.sendName) {
11987       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11988       SendToProgram(buf, &first);
11989     }
11990     SendToProgram(second.computerString, &second);
11991     if (second.sendName) {
11992       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11993       SendToProgram(buf, &second);
11994     }
11995
11996     ResetClocks();
11997     if (!first.sendTime || !second.sendTime) {
11998         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11999         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12000     }
12001     if (onmove->sendTime) {
12002       if (onmove->useColors) {
12003         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12004       }
12005       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12006     }
12007     if (onmove->useColors) {
12008       SendToProgram(onmove->twoMachinesColor, onmove);
12009     }
12010     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12011 //    SendToProgram("go\n", onmove);
12012     onmove->maybeThinking = TRUE;
12013     SetMachineThinkingEnables();
12014
12015     StartClocks();
12016
12017     if(bookHit) { // [HGM] book: simulate book reply
12018         static char bookMove[MSG_SIZ]; // a bit generous?
12019
12020         programStats.nodes = programStats.depth = programStats.time =
12021         programStats.score = programStats.got_only_move = 0;
12022         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12023
12024         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12025         strcat(bookMove, bookHit);
12026         savedMessage = bookMove; // args for deferred call
12027         savedState = onmove;
12028         ScheduleDelayedEvent(DeferredBookMove, 1);
12029     }
12030 }
12031
12032 void
12033 TrainingEvent()
12034 {
12035     if (gameMode == Training) {
12036       SetTrainingModeOff();
12037       gameMode = PlayFromGameFile;
12038       DisplayMessage("", _("Training mode off"));
12039     } else {
12040       gameMode = Training;
12041       animateTraining = appData.animate;
12042
12043       /* make sure we are not already at the end of the game */
12044       if (currentMove < forwardMostMove) {
12045         SetTrainingModeOn();
12046         DisplayMessage("", _("Training mode on"));
12047       } else {
12048         gameMode = PlayFromGameFile;
12049         DisplayError(_("Already at end of game"), 0);
12050       }
12051     }
12052     ModeHighlight();
12053 }
12054
12055 void
12056 IcsClientEvent()
12057 {
12058     if (!appData.icsActive) return;
12059     switch (gameMode) {
12060       case IcsPlayingWhite:
12061       case IcsPlayingBlack:
12062       case IcsObserving:
12063       case IcsIdle:
12064       case BeginningOfGame:
12065       case IcsExamining:
12066         return;
12067
12068       case EditGame:
12069         break;
12070
12071       case EditPosition:
12072         EditPositionDone(TRUE);
12073         break;
12074
12075       case AnalyzeMode:
12076       case AnalyzeFile:
12077         ExitAnalyzeMode();
12078         break;
12079
12080       default:
12081         EditGameEvent();
12082         break;
12083     }
12084
12085     gameMode = IcsIdle;
12086     ModeHighlight();
12087     return;
12088 }
12089
12090
12091 void
12092 EditGameEvent()
12093 {
12094     int i;
12095
12096     switch (gameMode) {
12097       case Training:
12098         SetTrainingModeOff();
12099         break;
12100       case MachinePlaysWhite:
12101       case MachinePlaysBlack:
12102       case BeginningOfGame:
12103         SendToProgram("force\n", &first);
12104         SetUserThinkingEnables();
12105         break;
12106       case PlayFromGameFile:
12107         (void) StopLoadGameTimer();
12108         if (gameFileFP != NULL) {
12109             gameFileFP = NULL;
12110         }
12111         break;
12112       case EditPosition:
12113         EditPositionDone(TRUE);
12114         break;
12115       case AnalyzeMode:
12116       case AnalyzeFile:
12117         ExitAnalyzeMode();
12118         SendToProgram("force\n", &first);
12119         break;
12120       case TwoMachinesPlay:
12121         GameEnds(EndOfFile, NULL, GE_PLAYER);
12122         ResurrectChessProgram();
12123         SetUserThinkingEnables();
12124         break;
12125       case EndOfGame:
12126         ResurrectChessProgram();
12127         break;
12128       case IcsPlayingBlack:
12129       case IcsPlayingWhite:
12130         DisplayError(_("Warning: You are still playing a game"), 0);
12131         break;
12132       case IcsObserving:
12133         DisplayError(_("Warning: You are still observing a game"), 0);
12134         break;
12135       case IcsExamining:
12136         DisplayError(_("Warning: You are still examining a game"), 0);
12137         break;
12138       case IcsIdle:
12139         break;
12140       case EditGame:
12141       default:
12142         return;
12143     }
12144
12145     pausing = FALSE;
12146     StopClocks();
12147     first.offeredDraw = second.offeredDraw = 0;
12148
12149     if (gameMode == PlayFromGameFile) {
12150         whiteTimeRemaining = timeRemaining[0][currentMove];
12151         blackTimeRemaining = timeRemaining[1][currentMove];
12152         DisplayTitle("");
12153     }
12154
12155     if (gameMode == MachinePlaysWhite ||
12156         gameMode == MachinePlaysBlack ||
12157         gameMode == TwoMachinesPlay ||
12158         gameMode == EndOfGame) {
12159         i = forwardMostMove;
12160         while (i > currentMove) {
12161             SendToProgram("undo\n", &first);
12162             i--;
12163         }
12164         whiteTimeRemaining = timeRemaining[0][currentMove];
12165         blackTimeRemaining = timeRemaining[1][currentMove];
12166         DisplayBothClocks();
12167         if (whiteFlag || blackFlag) {
12168             whiteFlag = blackFlag = 0;
12169         }
12170         DisplayTitle("");
12171     }
12172
12173     gameMode = EditGame;
12174     ModeHighlight();
12175     SetGameInfo();
12176 }
12177
12178
12179 void
12180 EditPositionEvent()
12181 {
12182     if (gameMode == EditPosition) {
12183         EditGameEvent();
12184         return;
12185     }
12186
12187     EditGameEvent();
12188     if (gameMode != EditGame) return;
12189
12190     gameMode = EditPosition;
12191     ModeHighlight();
12192     SetGameInfo();
12193     if (currentMove > 0)
12194       CopyBoard(boards[0], boards[currentMove]);
12195
12196     blackPlaysFirst = !WhiteOnMove(currentMove);
12197     ResetClocks();
12198     currentMove = forwardMostMove = backwardMostMove = 0;
12199     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12200     DisplayMove(-1);
12201 }
12202
12203 void
12204 ExitAnalyzeMode()
12205 {
12206     /* [DM] icsEngineAnalyze - possible call from other functions */
12207     if (appData.icsEngineAnalyze) {
12208         appData.icsEngineAnalyze = FALSE;
12209
12210         DisplayMessage("",_("Close ICS engine analyze..."));
12211     }
12212     if (first.analysisSupport && first.analyzing) {
12213       SendToProgram("exit\n", &first);
12214       first.analyzing = FALSE;
12215     }
12216     thinkOutput[0] = NULLCHAR;
12217 }
12218
12219 void
12220 EditPositionDone(Boolean fakeRights)
12221 {
12222     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12223
12224     startedFromSetupPosition = TRUE;
12225     InitChessProgram(&first, FALSE);
12226     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12227       boards[0][EP_STATUS] = EP_NONE;
12228       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12229     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12230         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12231         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12232       } else boards[0][CASTLING][2] = NoRights;
12233     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12234         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12235         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12236       } else boards[0][CASTLING][5] = NoRights;
12237     }
12238     SendToProgram("force\n", &first);
12239     if (blackPlaysFirst) {
12240         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12241         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12242         currentMove = forwardMostMove = backwardMostMove = 1;
12243         CopyBoard(boards[1], boards[0]);
12244     } else {
12245         currentMove = forwardMostMove = backwardMostMove = 0;
12246     }
12247     SendBoard(&first, forwardMostMove);
12248     if (appData.debugMode) {
12249         fprintf(debugFP, "EditPosDone\n");
12250     }
12251     DisplayTitle("");
12252     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12253     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12254     gameMode = EditGame;
12255     ModeHighlight();
12256     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12257     ClearHighlights(); /* [AS] */
12258 }
12259
12260 /* Pause for `ms' milliseconds */
12261 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12262 void
12263 TimeDelay(ms)
12264      long ms;
12265 {
12266     TimeMark m1, m2;
12267
12268     GetTimeMark(&m1);
12269     do {
12270         GetTimeMark(&m2);
12271     } while (SubtractTimeMarks(&m2, &m1) < ms);
12272 }
12273
12274 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12275 void
12276 SendMultiLineToICS(buf)
12277      char *buf;
12278 {
12279     char temp[MSG_SIZ+1], *p;
12280     int len;
12281
12282     len = strlen(buf);
12283     if (len > MSG_SIZ)
12284       len = MSG_SIZ;
12285
12286     strncpy(temp, buf, len);
12287     temp[len] = 0;
12288
12289     p = temp;
12290     while (*p) {
12291         if (*p == '\n' || *p == '\r')
12292           *p = ' ';
12293         ++p;
12294     }
12295
12296     strcat(temp, "\n");
12297     SendToICS(temp);
12298     SendToPlayer(temp, strlen(temp));
12299 }
12300
12301 void
12302 SetWhiteToPlayEvent()
12303 {
12304     if (gameMode == EditPosition) {
12305         blackPlaysFirst = FALSE;
12306         DisplayBothClocks();    /* works because currentMove is 0 */
12307     } else if (gameMode == IcsExamining) {
12308         SendToICS(ics_prefix);
12309         SendToICS("tomove white\n");
12310     }
12311 }
12312
12313 void
12314 SetBlackToPlayEvent()
12315 {
12316     if (gameMode == EditPosition) {
12317         blackPlaysFirst = TRUE;
12318         currentMove = 1;        /* kludge */
12319         DisplayBothClocks();
12320         currentMove = 0;
12321     } else if (gameMode == IcsExamining) {
12322         SendToICS(ics_prefix);
12323         SendToICS("tomove black\n");
12324     }
12325 }
12326
12327 void
12328 EditPositionMenuEvent(selection, x, y)
12329      ChessSquare selection;
12330      int x, y;
12331 {
12332     char buf[MSG_SIZ];
12333     ChessSquare piece = boards[0][y][x];
12334
12335     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12336
12337     switch (selection) {
12338       case ClearBoard:
12339         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12340             SendToICS(ics_prefix);
12341             SendToICS("bsetup clear\n");
12342         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12343             SendToICS(ics_prefix);
12344             SendToICS("clearboard\n");
12345         } else {
12346             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12347                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12348                 for (y = 0; y < BOARD_HEIGHT; y++) {
12349                     if (gameMode == IcsExamining) {
12350                         if (boards[currentMove][y][x] != EmptySquare) {
12351                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12352                                     AAA + x, ONE + y);
12353                             SendToICS(buf);
12354                         }
12355                     } else {
12356                         boards[0][y][x] = p;
12357                     }
12358                 }
12359             }
12360         }
12361         if (gameMode == EditPosition) {
12362             DrawPosition(FALSE, boards[0]);
12363         }
12364         break;
12365
12366       case WhitePlay:
12367         SetWhiteToPlayEvent();
12368         break;
12369
12370       case BlackPlay:
12371         SetBlackToPlayEvent();
12372         break;
12373
12374       case EmptySquare:
12375         if (gameMode == IcsExamining) {
12376             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12377             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12378             SendToICS(buf);
12379         } else {
12380             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12381                 if(x == BOARD_LEFT-2) {
12382                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12383                     boards[0][y][1] = 0;
12384                 } else
12385                 if(x == BOARD_RGHT+1) {
12386                     if(y >= gameInfo.holdingsSize) break;
12387                     boards[0][y][BOARD_WIDTH-2] = 0;
12388                 } else break;
12389             }
12390             boards[0][y][x] = EmptySquare;
12391             DrawPosition(FALSE, boards[0]);
12392         }
12393         break;
12394
12395       case PromotePiece:
12396         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12397            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12398             selection = (ChessSquare) (PROMOTED piece);
12399         } else if(piece == EmptySquare) selection = WhiteSilver;
12400         else selection = (ChessSquare)((int)piece - 1);
12401         goto defaultlabel;
12402
12403       case DemotePiece:
12404         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12405            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12406             selection = (ChessSquare) (DEMOTED piece);
12407         } else if(piece == EmptySquare) selection = BlackSilver;
12408         else selection = (ChessSquare)((int)piece + 1);
12409         goto defaultlabel;
12410
12411       case WhiteQueen:
12412       case BlackQueen:
12413         if(gameInfo.variant == VariantShatranj ||
12414            gameInfo.variant == VariantXiangqi  ||
12415            gameInfo.variant == VariantCourier  ||
12416            gameInfo.variant == VariantMakruk     )
12417             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12418         goto defaultlabel;
12419
12420       case WhiteKing:
12421       case BlackKing:
12422         if(gameInfo.variant == VariantXiangqi)
12423             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12424         if(gameInfo.variant == VariantKnightmate)
12425             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12426       default:
12427         defaultlabel:
12428         if (gameMode == IcsExamining) {
12429             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12430             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12431                      PieceToChar(selection), AAA + x, ONE + y);
12432             SendToICS(buf);
12433         } else {
12434             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12435                 int n;
12436                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12437                     n = PieceToNumber(selection - BlackPawn);
12438                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12439                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12440                     boards[0][BOARD_HEIGHT-1-n][1]++;
12441                 } else
12442                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12443                     n = PieceToNumber(selection);
12444                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12445                     boards[0][n][BOARD_WIDTH-1] = selection;
12446                     boards[0][n][BOARD_WIDTH-2]++;
12447                 }
12448             } else
12449             boards[0][y][x] = selection;
12450             DrawPosition(TRUE, boards[0]);
12451         }
12452         break;
12453     }
12454 }
12455
12456
12457 void
12458 DropMenuEvent(selection, x, y)
12459      ChessSquare selection;
12460      int x, y;
12461 {
12462     ChessMove moveType;
12463
12464     switch (gameMode) {
12465       case IcsPlayingWhite:
12466       case MachinePlaysBlack:
12467         if (!WhiteOnMove(currentMove)) {
12468             DisplayMoveError(_("It is Black's turn"));
12469             return;
12470         }
12471         moveType = WhiteDrop;
12472         break;
12473       case IcsPlayingBlack:
12474       case MachinePlaysWhite:
12475         if (WhiteOnMove(currentMove)) {
12476             DisplayMoveError(_("It is White's turn"));
12477             return;
12478         }
12479         moveType = BlackDrop;
12480         break;
12481       case EditGame:
12482         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12483         break;
12484       default:
12485         return;
12486     }
12487
12488     if (moveType == BlackDrop && selection < BlackPawn) {
12489       selection = (ChessSquare) ((int) selection
12490                                  + (int) BlackPawn - (int) WhitePawn);
12491     }
12492     if (boards[currentMove][y][x] != EmptySquare) {
12493         DisplayMoveError(_("That square is occupied"));
12494         return;
12495     }
12496
12497     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12498 }
12499
12500 void
12501 AcceptEvent()
12502 {
12503     /* Accept a pending offer of any kind from opponent */
12504
12505     if (appData.icsActive) {
12506         SendToICS(ics_prefix);
12507         SendToICS("accept\n");
12508     } else if (cmailMsgLoaded) {
12509         if (currentMove == cmailOldMove &&
12510             commentList[cmailOldMove] != NULL &&
12511             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12512                    "Black offers a draw" : "White offers a draw")) {
12513             TruncateGame();
12514             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12515             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12516         } else {
12517             DisplayError(_("There is no pending offer on this move"), 0);
12518             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12519         }
12520     } else {
12521         /* Not used for offers from chess program */
12522     }
12523 }
12524
12525 void
12526 DeclineEvent()
12527 {
12528     /* Decline a pending offer of any kind from opponent */
12529
12530     if (appData.icsActive) {
12531         SendToICS(ics_prefix);
12532         SendToICS("decline\n");
12533     } else if (cmailMsgLoaded) {
12534         if (currentMove == cmailOldMove &&
12535             commentList[cmailOldMove] != NULL &&
12536             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12537                    "Black offers a draw" : "White offers a draw")) {
12538 #ifdef NOTDEF
12539             AppendComment(cmailOldMove, "Draw declined", TRUE);
12540             DisplayComment(cmailOldMove - 1, "Draw declined");
12541 #endif /*NOTDEF*/
12542         } else {
12543             DisplayError(_("There is no pending offer on this move"), 0);
12544         }
12545     } else {
12546         /* Not used for offers from chess program */
12547     }
12548 }
12549
12550 void
12551 RematchEvent()
12552 {
12553     /* Issue ICS rematch command */
12554     if (appData.icsActive) {
12555         SendToICS(ics_prefix);
12556         SendToICS("rematch\n");
12557     }
12558 }
12559
12560 void
12561 CallFlagEvent()
12562 {
12563     /* Call your opponent's flag (claim a win on time) */
12564     if (appData.icsActive) {
12565         SendToICS(ics_prefix);
12566         SendToICS("flag\n");
12567     } else {
12568         switch (gameMode) {
12569           default:
12570             return;
12571           case MachinePlaysWhite:
12572             if (whiteFlag) {
12573                 if (blackFlag)
12574                   GameEnds(GameIsDrawn, "Both players ran out of time",
12575                            GE_PLAYER);
12576                 else
12577                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12578             } else {
12579                 DisplayError(_("Your opponent is not out of time"), 0);
12580             }
12581             break;
12582           case MachinePlaysBlack:
12583             if (blackFlag) {
12584                 if (whiteFlag)
12585                   GameEnds(GameIsDrawn, "Both players ran out of time",
12586                            GE_PLAYER);
12587                 else
12588                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12589             } else {
12590                 DisplayError(_("Your opponent is not out of time"), 0);
12591             }
12592             break;
12593         }
12594     }
12595 }
12596
12597 void
12598 DrawEvent()
12599 {
12600     /* Offer draw or accept pending draw offer from opponent */
12601
12602     if (appData.icsActive) {
12603         /* Note: tournament rules require draw offers to be
12604            made after you make your move but before you punch
12605            your clock.  Currently ICS doesn't let you do that;
12606            instead, you immediately punch your clock after making
12607            a move, but you can offer a draw at any time. */
12608
12609         SendToICS(ics_prefix);
12610         SendToICS("draw\n");
12611         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12612     } else if (cmailMsgLoaded) {
12613         if (currentMove == cmailOldMove &&
12614             commentList[cmailOldMove] != NULL &&
12615             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12616                    "Black offers a draw" : "White offers a draw")) {
12617             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12618             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12619         } else if (currentMove == cmailOldMove + 1) {
12620             char *offer = WhiteOnMove(cmailOldMove) ?
12621               "White offers a draw" : "Black offers a draw";
12622             AppendComment(currentMove, offer, TRUE);
12623             DisplayComment(currentMove - 1, offer);
12624             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12625         } else {
12626             DisplayError(_("You must make your move before offering a draw"), 0);
12627             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12628         }
12629     } else if (first.offeredDraw) {
12630         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12631     } else {
12632         if (first.sendDrawOffers) {
12633             SendToProgram("draw\n", &first);
12634             userOfferedDraw = TRUE;
12635         }
12636     }
12637 }
12638
12639 void
12640 AdjournEvent()
12641 {
12642     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12643
12644     if (appData.icsActive) {
12645         SendToICS(ics_prefix);
12646         SendToICS("adjourn\n");
12647     } else {
12648         /* Currently GNU Chess doesn't offer or accept Adjourns */
12649     }
12650 }
12651
12652
12653 void
12654 AbortEvent()
12655 {
12656     /* Offer Abort or accept pending Abort offer from opponent */
12657
12658     if (appData.icsActive) {
12659         SendToICS(ics_prefix);
12660         SendToICS("abort\n");
12661     } else {
12662         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12663     }
12664 }
12665
12666 void
12667 ResignEvent()
12668 {
12669     /* Resign.  You can do this even if it's not your turn. */
12670
12671     if (appData.icsActive) {
12672         SendToICS(ics_prefix);
12673         SendToICS("resign\n");
12674     } else {
12675         switch (gameMode) {
12676           case MachinePlaysWhite:
12677             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12678             break;
12679           case MachinePlaysBlack:
12680             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12681             break;
12682           case EditGame:
12683             if (cmailMsgLoaded) {
12684                 TruncateGame();
12685                 if (WhiteOnMove(cmailOldMove)) {
12686                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12687                 } else {
12688                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12689                 }
12690                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12691             }
12692             break;
12693           default:
12694             break;
12695         }
12696     }
12697 }
12698
12699
12700 void
12701 StopObservingEvent()
12702 {
12703     /* Stop observing current games */
12704     SendToICS(ics_prefix);
12705     SendToICS("unobserve\n");
12706 }
12707
12708 void
12709 StopExaminingEvent()
12710 {
12711     /* Stop observing current game */
12712     SendToICS(ics_prefix);
12713     SendToICS("unexamine\n");
12714 }
12715
12716 void
12717 ForwardInner(target)
12718      int target;
12719 {
12720     int limit;
12721
12722     if (appData.debugMode)
12723         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12724                 target, currentMove, forwardMostMove);
12725
12726     if (gameMode == EditPosition)
12727       return;
12728
12729     if (gameMode == PlayFromGameFile && !pausing)
12730       PauseEvent();
12731
12732     if (gameMode == IcsExamining && pausing)
12733       limit = pauseExamForwardMostMove;
12734     else
12735       limit = forwardMostMove;
12736
12737     if (target > limit) target = limit;
12738
12739     if (target > 0 && moveList[target - 1][0]) {
12740         int fromX, fromY, toX, toY;
12741         toX = moveList[target - 1][2] - AAA;
12742         toY = moveList[target - 1][3] - ONE;
12743         if (moveList[target - 1][1] == '@') {
12744             if (appData.highlightLastMove) {
12745                 SetHighlights(-1, -1, toX, toY);
12746             }
12747         } else {
12748             fromX = moveList[target - 1][0] - AAA;
12749             fromY = moveList[target - 1][1] - ONE;
12750             if (target == currentMove + 1) {
12751                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12752             }
12753             if (appData.highlightLastMove) {
12754                 SetHighlights(fromX, fromY, toX, toY);
12755             }
12756         }
12757     }
12758     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12759         gameMode == Training || gameMode == PlayFromGameFile ||
12760         gameMode == AnalyzeFile) {
12761         while (currentMove < target) {
12762             SendMoveToProgram(currentMove++, &first);
12763         }
12764     } else {
12765         currentMove = target;
12766     }
12767
12768     if (gameMode == EditGame || gameMode == EndOfGame) {
12769         whiteTimeRemaining = timeRemaining[0][currentMove];
12770         blackTimeRemaining = timeRemaining[1][currentMove];
12771     }
12772     DisplayBothClocks();
12773     DisplayMove(currentMove - 1);
12774     DrawPosition(FALSE, boards[currentMove]);
12775     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12776     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12777         DisplayComment(currentMove - 1, commentList[currentMove]);
12778     }
12779 }
12780
12781
12782 void
12783 ForwardEvent()
12784 {
12785     if (gameMode == IcsExamining && !pausing) {
12786         SendToICS(ics_prefix);
12787         SendToICS("forward\n");
12788     } else {
12789         ForwardInner(currentMove + 1);
12790     }
12791 }
12792
12793 void
12794 ToEndEvent()
12795 {
12796     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12797         /* to optimze, we temporarily turn off analysis mode while we feed
12798          * the remaining moves to the engine. Otherwise we get analysis output
12799          * after each move.
12800          */
12801         if (first.analysisSupport) {
12802           SendToProgram("exit\nforce\n", &first);
12803           first.analyzing = FALSE;
12804         }
12805     }
12806
12807     if (gameMode == IcsExamining && !pausing) {
12808         SendToICS(ics_prefix);
12809         SendToICS("forward 999999\n");
12810     } else {
12811         ForwardInner(forwardMostMove);
12812     }
12813
12814     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12815         /* we have fed all the moves, so reactivate analysis mode */
12816         SendToProgram("analyze\n", &first);
12817         first.analyzing = TRUE;
12818         /*first.maybeThinking = TRUE;*/
12819         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12820     }
12821 }
12822
12823 void
12824 BackwardInner(target)
12825      int target;
12826 {
12827     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12828
12829     if (appData.debugMode)
12830         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12831                 target, currentMove, forwardMostMove);
12832
12833     if (gameMode == EditPosition) return;
12834     if (currentMove <= backwardMostMove) {
12835         ClearHighlights();
12836         DrawPosition(full_redraw, boards[currentMove]);
12837         return;
12838     }
12839     if (gameMode == PlayFromGameFile && !pausing)
12840       PauseEvent();
12841
12842     if (moveList[target][0]) {
12843         int fromX, fromY, toX, toY;
12844         toX = moveList[target][2] - AAA;
12845         toY = moveList[target][3] - ONE;
12846         if (moveList[target][1] == '@') {
12847             if (appData.highlightLastMove) {
12848                 SetHighlights(-1, -1, toX, toY);
12849             }
12850         } else {
12851             fromX = moveList[target][0] - AAA;
12852             fromY = moveList[target][1] - ONE;
12853             if (target == currentMove - 1) {
12854                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12855             }
12856             if (appData.highlightLastMove) {
12857                 SetHighlights(fromX, fromY, toX, toY);
12858             }
12859         }
12860     }
12861     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12862         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12863         while (currentMove > target) {
12864             SendToProgram("undo\n", &first);
12865             currentMove--;
12866         }
12867     } else {
12868         currentMove = target;
12869     }
12870
12871     if (gameMode == EditGame || gameMode == EndOfGame) {
12872         whiteTimeRemaining = timeRemaining[0][currentMove];
12873         blackTimeRemaining = timeRemaining[1][currentMove];
12874     }
12875     DisplayBothClocks();
12876     DisplayMove(currentMove - 1);
12877     DrawPosition(full_redraw, boards[currentMove]);
12878     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12879     // [HGM] PV info: routine tests if comment empty
12880     DisplayComment(currentMove - 1, commentList[currentMove]);
12881 }
12882
12883 void
12884 BackwardEvent()
12885 {
12886     if (gameMode == IcsExamining && !pausing) {
12887         SendToICS(ics_prefix);
12888         SendToICS("backward\n");
12889     } else {
12890         BackwardInner(currentMove - 1);
12891     }
12892 }
12893
12894 void
12895 ToStartEvent()
12896 {
12897     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12898         /* to optimize, we temporarily turn off analysis mode while we undo
12899          * all the moves. Otherwise we get analysis output after each undo.
12900          */
12901         if (first.analysisSupport) {
12902           SendToProgram("exit\nforce\n", &first);
12903           first.analyzing = FALSE;
12904         }
12905     }
12906
12907     if (gameMode == IcsExamining && !pausing) {
12908         SendToICS(ics_prefix);
12909         SendToICS("backward 999999\n");
12910     } else {
12911         BackwardInner(backwardMostMove);
12912     }
12913
12914     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12915         /* we have fed all the moves, so reactivate analysis mode */
12916         SendToProgram("analyze\n", &first);
12917         first.analyzing = TRUE;
12918         /*first.maybeThinking = TRUE;*/
12919         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12920     }
12921 }
12922
12923 void
12924 ToNrEvent(int to)
12925 {
12926   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12927   if (to >= forwardMostMove) to = forwardMostMove;
12928   if (to <= backwardMostMove) to = backwardMostMove;
12929   if (to < currentMove) {
12930     BackwardInner(to);
12931   } else {
12932     ForwardInner(to);
12933   }
12934 }
12935
12936 void
12937 RevertEvent(Boolean annotate)
12938 {
12939     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12940         return;
12941     }
12942     if (gameMode != IcsExamining) {
12943         DisplayError(_("You are not examining a game"), 0);
12944         return;
12945     }
12946     if (pausing) {
12947         DisplayError(_("You can't revert while pausing"), 0);
12948         return;
12949     }
12950     SendToICS(ics_prefix);
12951     SendToICS("revert\n");
12952 }
12953
12954 void
12955 RetractMoveEvent()
12956 {
12957     switch (gameMode) {
12958       case MachinePlaysWhite:
12959       case MachinePlaysBlack:
12960         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12961             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12962             return;
12963         }
12964         if (forwardMostMove < 2) return;
12965         currentMove = forwardMostMove = forwardMostMove - 2;
12966         whiteTimeRemaining = timeRemaining[0][currentMove];
12967         blackTimeRemaining = timeRemaining[1][currentMove];
12968         DisplayBothClocks();
12969         DisplayMove(currentMove - 1);
12970         ClearHighlights();/*!! could figure this out*/
12971         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12972         SendToProgram("remove\n", &first);
12973         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12974         break;
12975
12976       case BeginningOfGame:
12977       default:
12978         break;
12979
12980       case IcsPlayingWhite:
12981       case IcsPlayingBlack:
12982         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12983             SendToICS(ics_prefix);
12984             SendToICS("takeback 2\n");
12985         } else {
12986             SendToICS(ics_prefix);
12987             SendToICS("takeback 1\n");
12988         }
12989         break;
12990     }
12991 }
12992
12993 void
12994 MoveNowEvent()
12995 {
12996     ChessProgramState *cps;
12997
12998     switch (gameMode) {
12999       case MachinePlaysWhite:
13000         if (!WhiteOnMove(forwardMostMove)) {
13001             DisplayError(_("It is your turn"), 0);
13002             return;
13003         }
13004         cps = &first;
13005         break;
13006       case MachinePlaysBlack:
13007         if (WhiteOnMove(forwardMostMove)) {
13008             DisplayError(_("It is your turn"), 0);
13009             return;
13010         }
13011         cps = &first;
13012         break;
13013       case TwoMachinesPlay:
13014         if (WhiteOnMove(forwardMostMove) ==
13015             (first.twoMachinesColor[0] == 'w')) {
13016             cps = &first;
13017         } else {
13018             cps = &second;
13019         }
13020         break;
13021       case BeginningOfGame:
13022       default:
13023         return;
13024     }
13025     SendToProgram("?\n", cps);
13026 }
13027
13028 void
13029 TruncateGameEvent()
13030 {
13031     EditGameEvent();
13032     if (gameMode != EditGame) return;
13033     TruncateGame();
13034 }
13035
13036 void
13037 TruncateGame()
13038 {
13039     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13040     if (forwardMostMove > currentMove) {
13041         if (gameInfo.resultDetails != NULL) {
13042             free(gameInfo.resultDetails);
13043             gameInfo.resultDetails = NULL;
13044             gameInfo.result = GameUnfinished;
13045         }
13046         forwardMostMove = currentMove;
13047         HistorySet(parseList, backwardMostMove, forwardMostMove,
13048                    currentMove-1);
13049     }
13050 }
13051
13052 void
13053 HintEvent()
13054 {
13055     if (appData.noChessProgram) return;
13056     switch (gameMode) {
13057       case MachinePlaysWhite:
13058         if (WhiteOnMove(forwardMostMove)) {
13059             DisplayError(_("Wait until your turn"), 0);
13060             return;
13061         }
13062         break;
13063       case BeginningOfGame:
13064       case MachinePlaysBlack:
13065         if (!WhiteOnMove(forwardMostMove)) {
13066             DisplayError(_("Wait until your turn"), 0);
13067             return;
13068         }
13069         break;
13070       default:
13071         DisplayError(_("No hint available"), 0);
13072         return;
13073     }
13074     SendToProgram("hint\n", &first);
13075     hintRequested = TRUE;
13076 }
13077
13078 void
13079 BookEvent()
13080 {
13081     if (appData.noChessProgram) return;
13082     switch (gameMode) {
13083       case MachinePlaysWhite:
13084         if (WhiteOnMove(forwardMostMove)) {
13085             DisplayError(_("Wait until your turn"), 0);
13086             return;
13087         }
13088         break;
13089       case BeginningOfGame:
13090       case MachinePlaysBlack:
13091         if (!WhiteOnMove(forwardMostMove)) {
13092             DisplayError(_("Wait until your turn"), 0);
13093             return;
13094         }
13095         break;
13096       case EditPosition:
13097         EditPositionDone(TRUE);
13098         break;
13099       case TwoMachinesPlay:
13100         return;
13101       default:
13102         break;
13103     }
13104     SendToProgram("bk\n", &first);
13105     bookOutput[0] = NULLCHAR;
13106     bookRequested = TRUE;
13107 }
13108
13109 void
13110 AboutGameEvent()
13111 {
13112     char *tags = PGNTags(&gameInfo);
13113     TagsPopUp(tags, CmailMsg());
13114     free(tags);
13115 }
13116
13117 /* end button procedures */
13118
13119 void
13120 PrintPosition(fp, move)
13121      FILE *fp;
13122      int move;
13123 {
13124     int i, j;
13125
13126     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13127         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13128             char c = PieceToChar(boards[move][i][j]);
13129             fputc(c == 'x' ? '.' : c, fp);
13130             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13131         }
13132     }
13133     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13134       fprintf(fp, "white to play\n");
13135     else
13136       fprintf(fp, "black to play\n");
13137 }
13138
13139 void
13140 PrintOpponents(fp)
13141      FILE *fp;
13142 {
13143     if (gameInfo.white != NULL) {
13144         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13145     } else {
13146         fprintf(fp, "\n");
13147     }
13148 }
13149
13150 /* Find last component of program's own name, using some heuristics */
13151 void
13152 TidyProgramName(prog, host, buf)
13153      char *prog, *host, buf[MSG_SIZ];
13154 {
13155     char *p, *q;
13156     int local = (strcmp(host, "localhost") == 0);
13157     while (!local && (p = strchr(prog, ';')) != NULL) {
13158         p++;
13159         while (*p == ' ') p++;
13160         prog = p;
13161     }
13162     if (*prog == '"' || *prog == '\'') {
13163         q = strchr(prog + 1, *prog);
13164     } else {
13165         q = strchr(prog, ' ');
13166     }
13167     if (q == NULL) q = prog + strlen(prog);
13168     p = q;
13169     while (p >= prog && *p != '/' && *p != '\\') p--;
13170     p++;
13171     if(p == prog && *p == '"') p++;
13172     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13173     memcpy(buf, p, q - p);
13174     buf[q - p] = NULLCHAR;
13175     if (!local) {
13176         strcat(buf, "@");
13177         strcat(buf, host);
13178     }
13179 }
13180
13181 char *
13182 TimeControlTagValue()
13183 {
13184     char buf[MSG_SIZ];
13185     if (!appData.clockMode) {
13186       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13187     } else if (movesPerSession > 0) {
13188       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13189     } else if (timeIncrement == 0) {
13190       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13191     } else {
13192       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13193     }
13194     return StrSave(buf);
13195 }
13196
13197 void
13198 SetGameInfo()
13199 {
13200     /* This routine is used only for certain modes */
13201     VariantClass v = gameInfo.variant;
13202     ChessMove r = GameUnfinished;
13203     char *p = NULL;
13204
13205     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13206         r = gameInfo.result;
13207         p = gameInfo.resultDetails;
13208         gameInfo.resultDetails = NULL;
13209     }
13210     ClearGameInfo(&gameInfo);
13211     gameInfo.variant = v;
13212
13213     switch (gameMode) {
13214       case MachinePlaysWhite:
13215         gameInfo.event = StrSave( appData.pgnEventHeader );
13216         gameInfo.site = StrSave(HostName());
13217         gameInfo.date = PGNDate();
13218         gameInfo.round = StrSave("-");
13219         gameInfo.white = StrSave(first.tidy);
13220         gameInfo.black = StrSave(UserName());
13221         gameInfo.timeControl = TimeControlTagValue();
13222         break;
13223
13224       case MachinePlaysBlack:
13225         gameInfo.event = StrSave( appData.pgnEventHeader );
13226         gameInfo.site = StrSave(HostName());
13227         gameInfo.date = PGNDate();
13228         gameInfo.round = StrSave("-");
13229         gameInfo.white = StrSave(UserName());
13230         gameInfo.black = StrSave(first.tidy);
13231         gameInfo.timeControl = TimeControlTagValue();
13232         break;
13233
13234       case TwoMachinesPlay:
13235         gameInfo.event = StrSave( appData.pgnEventHeader );
13236         gameInfo.site = StrSave(HostName());
13237         gameInfo.date = PGNDate();
13238         if (matchGame > 0) {
13239             char buf[MSG_SIZ];
13240             snprintf(buf, MSG_SIZ, "%d", matchGame);
13241             gameInfo.round = StrSave(buf);
13242         } else {
13243             gameInfo.round = StrSave("-");
13244         }
13245         if (first.twoMachinesColor[0] == 'w') {
13246             gameInfo.white = StrSave(first.tidy);
13247             gameInfo.black = StrSave(second.tidy);
13248         } else {
13249             gameInfo.white = StrSave(second.tidy);
13250             gameInfo.black = StrSave(first.tidy);
13251         }
13252         gameInfo.timeControl = TimeControlTagValue();
13253         break;
13254
13255       case EditGame:
13256         gameInfo.event = StrSave("Edited game");
13257         gameInfo.site = StrSave(HostName());
13258         gameInfo.date = PGNDate();
13259         gameInfo.round = StrSave("-");
13260         gameInfo.white = StrSave("-");
13261         gameInfo.black = StrSave("-");
13262         gameInfo.result = r;
13263         gameInfo.resultDetails = p;
13264         break;
13265
13266       case EditPosition:
13267         gameInfo.event = StrSave("Edited position");
13268         gameInfo.site = StrSave(HostName());
13269         gameInfo.date = PGNDate();
13270         gameInfo.round = StrSave("-");
13271         gameInfo.white = StrSave("-");
13272         gameInfo.black = StrSave("-");
13273         break;
13274
13275       case IcsPlayingWhite:
13276       case IcsPlayingBlack:
13277       case IcsObserving:
13278       case IcsExamining:
13279         break;
13280
13281       case PlayFromGameFile:
13282         gameInfo.event = StrSave("Game from non-PGN file");
13283         gameInfo.site = StrSave(HostName());
13284         gameInfo.date = PGNDate();
13285         gameInfo.round = StrSave("-");
13286         gameInfo.white = StrSave("?");
13287         gameInfo.black = StrSave("?");
13288         break;
13289
13290       default:
13291         break;
13292     }
13293 }
13294
13295 void
13296 ReplaceComment(index, text)
13297      int index;
13298      char *text;
13299 {
13300     int len;
13301
13302     while (*text == '\n') text++;
13303     len = strlen(text);
13304     while (len > 0 && text[len - 1] == '\n') len--;
13305
13306     if (commentList[index] != NULL)
13307       free(commentList[index]);
13308
13309     if (len == 0) {
13310         commentList[index] = NULL;
13311         return;
13312     }
13313   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13314       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13315       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13316     commentList[index] = (char *) malloc(len + 2);
13317     strncpy(commentList[index], text, len);
13318     commentList[index][len] = '\n';
13319     commentList[index][len + 1] = NULLCHAR;
13320   } else {
13321     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13322     char *p;
13323     commentList[index] = (char *) malloc(len + 7);
13324     safeStrCpy(commentList[index], "{\n", 3);
13325     safeStrCpy(commentList[index]+2, text, len+1);
13326     commentList[index][len+2] = NULLCHAR;
13327     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13328     strcat(commentList[index], "\n}\n");
13329   }
13330 }
13331
13332 void
13333 CrushCRs(text)
13334      char *text;
13335 {
13336   char *p = text;
13337   char *q = text;
13338   char ch;
13339
13340   do {
13341     ch = *p++;
13342     if (ch == '\r') continue;
13343     *q++ = ch;
13344   } while (ch != '\0');
13345 }
13346
13347 void
13348 AppendComment(index, text, addBraces)
13349      int index;
13350      char *text;
13351      Boolean addBraces; // [HGM] braces: tells if we should add {}
13352 {
13353     int oldlen, len;
13354     char *old;
13355
13356 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13357     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13358
13359     CrushCRs(text);
13360     while (*text == '\n') text++;
13361     len = strlen(text);
13362     while (len > 0 && text[len - 1] == '\n') len--;
13363
13364     if (len == 0) return;
13365
13366     if (commentList[index] != NULL) {
13367         old = commentList[index];
13368         oldlen = strlen(old);
13369         while(commentList[index][oldlen-1] ==  '\n')
13370           commentList[index][--oldlen] = NULLCHAR;
13371         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13372         safeStrCpy(commentList[index], old, oldlen);
13373         free(old);
13374         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13375         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13376           if(addBraces) addBraces = FALSE; else { text++; len--; }
13377           while (*text == '\n') { text++; len--; }
13378           commentList[index][--oldlen] = NULLCHAR;
13379       }
13380         if(addBraces) strcat(commentList[index], "\n{\n");
13381         else          strcat(commentList[index], "\n");
13382         strcat(commentList[index], text);
13383         if(addBraces) strcat(commentList[index], "\n}\n");
13384         else          strcat(commentList[index], "\n");
13385     } else {
13386         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13387         if(addBraces)
13388           safeStrCpy(commentList[index], "{\n", 3);
13389         else commentList[index][0] = NULLCHAR;
13390         strcat(commentList[index], text);
13391         strcat(commentList[index], "\n");
13392         if(addBraces) strcat(commentList[index], "}\n");
13393     }
13394 }
13395
13396 static char * FindStr( char * text, char * sub_text )
13397 {
13398     char * result = strstr( text, sub_text );
13399
13400     if( result != NULL ) {
13401         result += strlen( sub_text );
13402     }
13403
13404     return result;
13405 }
13406
13407 /* [AS] Try to extract PV info from PGN comment */
13408 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13409 char *GetInfoFromComment( int index, char * text )
13410 {
13411     char * sep = text;
13412
13413     if( text != NULL && index > 0 ) {
13414         int score = 0;
13415         int depth = 0;
13416         int time = -1, sec = 0, deci;
13417         char * s_eval = FindStr( text, "[%eval " );
13418         char * s_emt = FindStr( text, "[%emt " );
13419
13420         if( s_eval != NULL || s_emt != NULL ) {
13421             /* New style */
13422             char delim;
13423
13424             if( s_eval != NULL ) {
13425                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13426                     return text;
13427                 }
13428
13429                 if( delim != ']' ) {
13430                     return text;
13431                 }
13432             }
13433
13434             if( s_emt != NULL ) {
13435             }
13436                 return text;
13437         }
13438         else {
13439             /* We expect something like: [+|-]nnn.nn/dd */
13440             int score_lo = 0;
13441
13442             if(*text != '{') return text; // [HGM] braces: must be normal comment
13443
13444             sep = strchr( text, '/' );
13445             if( sep == NULL || sep < (text+4) ) {
13446                 return text;
13447             }
13448
13449             time = -1; sec = -1; deci = -1;
13450             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13451                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13452                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13453                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13454                 return text;
13455             }
13456
13457             if( score_lo < 0 || score_lo >= 100 ) {
13458                 return text;
13459             }
13460
13461             if(sec >= 0) time = 600*time + 10*sec; else
13462             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13463
13464             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13465
13466             /* [HGM] PV time: now locate end of PV info */
13467             while( *++sep >= '0' && *sep <= '9'); // strip depth
13468             if(time >= 0)
13469             while( *++sep >= '0' && *sep <= '9'); // strip time
13470             if(sec >= 0)
13471             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13472             if(deci >= 0)
13473             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13474             while(*sep == ' ') sep++;
13475         }
13476
13477         if( depth <= 0 ) {
13478             return text;
13479         }
13480
13481         if( time < 0 ) {
13482             time = -1;
13483         }
13484
13485         pvInfoList[index-1].depth = depth;
13486         pvInfoList[index-1].score = score;
13487         pvInfoList[index-1].time  = 10*time; // centi-sec
13488         if(*sep == '}') *sep = 0; else *--sep = '{';
13489     }
13490     return sep;
13491 }
13492
13493 void
13494 SendToProgram(message, cps)
13495      char *message;
13496      ChessProgramState *cps;
13497 {
13498     int count, outCount, error;
13499     char buf[MSG_SIZ];
13500
13501     if (cps->pr == NULL) return;
13502     Attention(cps);
13503
13504     if (appData.debugMode) {
13505         TimeMark now;
13506         GetTimeMark(&now);
13507         fprintf(debugFP, "%ld >%-6s: %s",
13508                 SubtractTimeMarks(&now, &programStartTime),
13509                 cps->which, message);
13510     }
13511
13512     count = strlen(message);
13513     outCount = OutputToProcess(cps->pr, message, count, &error);
13514     if (outCount < count && !exiting
13515                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13516       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13517         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13518             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13519                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13520                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13521             } else {
13522                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13523             }
13524             gameInfo.resultDetails = StrSave(buf);
13525         }
13526         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13527     }
13528 }
13529
13530 void
13531 ReceiveFromProgram(isr, closure, message, count, error)
13532      InputSourceRef isr;
13533      VOIDSTAR closure;
13534      char *message;
13535      int count;
13536      int error;
13537 {
13538     char *end_str;
13539     char buf[MSG_SIZ];
13540     ChessProgramState *cps = (ChessProgramState *)closure;
13541
13542     if (isr != cps->isr) return; /* Killed intentionally */
13543     if (count <= 0) {
13544         if (count == 0) {
13545             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13546                     cps->which, cps->program);
13547         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13548                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13549                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13550                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13551                 } else {
13552                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13553                 }
13554                 gameInfo.resultDetails = StrSave(buf);
13555             }
13556             RemoveInputSource(cps->isr);
13557             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13558         } else {
13559             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13560                     cps->which, cps->program);
13561             RemoveInputSource(cps->isr);
13562
13563             /* [AS] Program is misbehaving badly... kill it */
13564             if( count == -2 ) {
13565                 DestroyChildProcess( cps->pr, 9 );
13566                 cps->pr = NoProc;
13567             }
13568
13569             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13570         }
13571         return;
13572     }
13573
13574     if ((end_str = strchr(message, '\r')) != NULL)
13575       *end_str = NULLCHAR;
13576     if ((end_str = strchr(message, '\n')) != NULL)
13577       *end_str = NULLCHAR;
13578
13579     if (appData.debugMode) {
13580         TimeMark now; int print = 1;
13581         char *quote = ""; char c; int i;
13582
13583         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13584                 char start = message[0];
13585                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13586                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13587                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13588                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13589                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13590                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13591                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13592                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13593                    sscanf(message, "hint: %c", &c)!=1 && 
13594                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13595                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13596                     print = (appData.engineComments >= 2);
13597                 }
13598                 message[0] = start; // restore original message
13599         }
13600         if(print) {
13601                 GetTimeMark(&now);
13602                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13603                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13604                         quote,
13605                         message);
13606         }
13607     }
13608
13609     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13610     if (appData.icsEngineAnalyze) {
13611         if (strstr(message, "whisper") != NULL ||
13612              strstr(message, "kibitz") != NULL ||
13613             strstr(message, "tellics") != NULL) return;
13614     }
13615
13616     HandleMachineMove(message, cps);
13617 }
13618
13619
13620 void
13621 SendTimeControl(cps, mps, tc, inc, sd, st)
13622      ChessProgramState *cps;
13623      int mps, inc, sd, st;
13624      long tc;
13625 {
13626     char buf[MSG_SIZ];
13627     int seconds;
13628
13629     if( timeControl_2 > 0 ) {
13630         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13631             tc = timeControl_2;
13632         }
13633     }
13634     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13635     inc /= cps->timeOdds;
13636     st  /= cps->timeOdds;
13637
13638     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13639
13640     if (st > 0) {
13641       /* Set exact time per move, normally using st command */
13642       if (cps->stKludge) {
13643         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13644         seconds = st % 60;
13645         if (seconds == 0) {
13646           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13647         } else {
13648           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13649         }
13650       } else {
13651         snprintf(buf, MSG_SIZ, "st %d\n", st);
13652       }
13653     } else {
13654       /* Set conventional or incremental time control, using level command */
13655       if (seconds == 0) {
13656         /* Note old gnuchess bug -- minutes:seconds used to not work.
13657            Fixed in later versions, but still avoid :seconds
13658            when seconds is 0. */
13659         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13660       } else {
13661         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13662                  seconds, inc/1000.);
13663       }
13664     }
13665     SendToProgram(buf, cps);
13666
13667     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13668     /* Orthogonally, limit search to given depth */
13669     if (sd > 0) {
13670       if (cps->sdKludge) {
13671         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13672       } else {
13673         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13674       }
13675       SendToProgram(buf, cps);
13676     }
13677
13678     if(cps->nps > 0) { /* [HGM] nps */
13679         if(cps->supportsNPS == FALSE)
13680           cps->nps = -1; // don't use if engine explicitly says not supported!
13681         else {
13682           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13683           SendToProgram(buf, cps);
13684         }
13685     }
13686 }
13687
13688 ChessProgramState *WhitePlayer()
13689 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13690 {
13691     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13692        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13693         return &second;
13694     return &first;
13695 }
13696
13697 void
13698 SendTimeRemaining(cps, machineWhite)
13699      ChessProgramState *cps;
13700      int /*boolean*/ machineWhite;
13701 {
13702     char message[MSG_SIZ];
13703     long time, otime;
13704
13705     /* Note: this routine must be called when the clocks are stopped
13706        or when they have *just* been set or switched; otherwise
13707        it will be off by the time since the current tick started.
13708     */
13709     if (machineWhite) {
13710         time = whiteTimeRemaining / 10;
13711         otime = blackTimeRemaining / 10;
13712     } else {
13713         time = blackTimeRemaining / 10;
13714         otime = whiteTimeRemaining / 10;
13715     }
13716     /* [HGM] translate opponent's time by time-odds factor */
13717     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13718     if (appData.debugMode) {
13719         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13720     }
13721
13722     if (time <= 0) time = 1;
13723     if (otime <= 0) otime = 1;
13724
13725     snprintf(message, MSG_SIZ, "time %ld\n", time);
13726     SendToProgram(message, cps);
13727
13728     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13729     SendToProgram(message, cps);
13730 }
13731
13732 int
13733 BoolFeature(p, name, loc, cps)
13734      char **p;
13735      char *name;
13736      int *loc;
13737      ChessProgramState *cps;
13738 {
13739   char buf[MSG_SIZ];
13740   int len = strlen(name);
13741   int val;
13742
13743   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13744     (*p) += len + 1;
13745     sscanf(*p, "%d", &val);
13746     *loc = (val != 0);
13747     while (**p && **p != ' ')
13748       (*p)++;
13749     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13750     SendToProgram(buf, cps);
13751     return TRUE;
13752   }
13753   return FALSE;
13754 }
13755
13756 int
13757 IntFeature(p, name, loc, cps)
13758      char **p;
13759      char *name;
13760      int *loc;
13761      ChessProgramState *cps;
13762 {
13763   char buf[MSG_SIZ];
13764   int len = strlen(name);
13765   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13766     (*p) += len + 1;
13767     sscanf(*p, "%d", loc);
13768     while (**p && **p != ' ') (*p)++;
13769     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13770     SendToProgram(buf, cps);
13771     return TRUE;
13772   }
13773   return FALSE;
13774 }
13775
13776 int
13777 StringFeature(p, name, loc, cps)
13778      char **p;
13779      char *name;
13780      char loc[];
13781      ChessProgramState *cps;
13782 {
13783   char buf[MSG_SIZ];
13784   int len = strlen(name);
13785   if (strncmp((*p), name, len) == 0
13786       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13787     (*p) += len + 2;
13788     sscanf(*p, "%[^\"]", loc);
13789     while (**p && **p != '\"') (*p)++;
13790     if (**p == '\"') (*p)++;
13791     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13792     SendToProgram(buf, cps);
13793     return TRUE;
13794   }
13795   return FALSE;
13796 }
13797
13798 int
13799 ParseOption(Option *opt, ChessProgramState *cps)
13800 // [HGM] options: process the string that defines an engine option, and determine
13801 // name, type, default value, and allowed value range
13802 {
13803         char *p, *q, buf[MSG_SIZ];
13804         int n, min = (-1)<<31, max = 1<<31, def;
13805
13806         if(p = strstr(opt->name, " -spin ")) {
13807             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13808             if(max < min) max = min; // enforce consistency
13809             if(def < min) def = min;
13810             if(def > max) def = max;
13811             opt->value = def;
13812             opt->min = min;
13813             opt->max = max;
13814             opt->type = Spin;
13815         } else if((p = strstr(opt->name, " -slider "))) {
13816             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13817             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13818             if(max < min) max = min; // enforce consistency
13819             if(def < min) def = min;
13820             if(def > max) def = max;
13821             opt->value = def;
13822             opt->min = min;
13823             opt->max = max;
13824             opt->type = Spin; // Slider;
13825         } else if((p = strstr(opt->name, " -string "))) {
13826             opt->textValue = p+9;
13827             opt->type = TextBox;
13828         } else if((p = strstr(opt->name, " -file "))) {
13829             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13830             opt->textValue = p+7;
13831             opt->type = TextBox; // FileName;
13832         } else if((p = strstr(opt->name, " -path "))) {
13833             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13834             opt->textValue = p+7;
13835             opt->type = TextBox; // PathName;
13836         } else if(p = strstr(opt->name, " -check ")) {
13837             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13838             opt->value = (def != 0);
13839             opt->type = CheckBox;
13840         } else if(p = strstr(opt->name, " -combo ")) {
13841             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13842             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13843             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13844             opt->value = n = 0;
13845             while(q = StrStr(q, " /// ")) {
13846                 n++; *q = 0;    // count choices, and null-terminate each of them
13847                 q += 5;
13848                 if(*q == '*') { // remember default, which is marked with * prefix
13849                     q++;
13850                     opt->value = n;
13851                 }
13852                 cps->comboList[cps->comboCnt++] = q;
13853             }
13854             cps->comboList[cps->comboCnt++] = NULL;
13855             opt->max = n + 1;
13856             opt->type = ComboBox;
13857         } else if(p = strstr(opt->name, " -button")) {
13858             opt->type = Button;
13859         } else if(p = strstr(opt->name, " -save")) {
13860             opt->type = SaveButton;
13861         } else return FALSE;
13862         *p = 0; // terminate option name
13863         // now look if the command-line options define a setting for this engine option.
13864         if(cps->optionSettings && cps->optionSettings[0])
13865             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13866         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13867           snprintf(buf, MSG_SIZ, "option %s", p);
13868                 if(p = strstr(buf, ",")) *p = 0;
13869                 if(q = strchr(buf, '=')) switch(opt->type) {
13870                     case ComboBox:
13871                         for(n=0; n<opt->max; n++)
13872                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13873                         break;
13874                     case TextBox:
13875                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13876                         break;
13877                     case Spin:
13878                     case CheckBox:
13879                         opt->value = atoi(q+1);
13880                     default:
13881                         break;
13882                 }
13883                 strcat(buf, "\n");
13884                 SendToProgram(buf, cps);
13885         }
13886         return TRUE;
13887 }
13888
13889 void
13890 FeatureDone(cps, val)
13891      ChessProgramState* cps;
13892      int val;
13893 {
13894   DelayedEventCallback cb = GetDelayedEvent();
13895   if ((cb == InitBackEnd3 && cps == &first) ||
13896       (cb == SettingsMenuIfReady && cps == &second) ||
13897       (cb == TwoMachinesEventIfReady && cps == &second)) {
13898     CancelDelayedEvent();
13899     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13900   }
13901   cps->initDone = val;
13902 }
13903
13904 /* Parse feature command from engine */
13905 void
13906 ParseFeatures(args, cps)
13907      char* args;
13908      ChessProgramState *cps;
13909 {
13910   char *p = args;
13911   char *q;
13912   int val;
13913   char buf[MSG_SIZ];
13914
13915   for (;;) {
13916     while (*p == ' ') p++;
13917     if (*p == NULLCHAR) return;
13918
13919     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13920     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13921     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13922     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13923     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13924     if (BoolFeature(&p, "reuse", &val, cps)) {
13925       /* Engine can disable reuse, but can't enable it if user said no */
13926       if (!val) cps->reuse = FALSE;
13927       continue;
13928     }
13929     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13930     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13931       if (gameMode == TwoMachinesPlay) {
13932         DisplayTwoMachinesTitle();
13933       } else {
13934         DisplayTitle("");
13935       }
13936       continue;
13937     }
13938     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13939     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13940     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13941     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13942     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13943     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13944     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13945     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13946     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13947     if (IntFeature(&p, "done", &val, cps)) {
13948       FeatureDone(cps, val);
13949       continue;
13950     }
13951     /* Added by Tord: */
13952     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13953     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13954     /* End of additions by Tord */
13955
13956     /* [HGM] added features: */
13957     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13958     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13959     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13960     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13961     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13962     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13963     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13964         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13965           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13966             SendToProgram(buf, cps);
13967             continue;
13968         }
13969         if(cps->nrOptions >= MAX_OPTIONS) {
13970             cps->nrOptions--;
13971             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13972             DisplayError(buf, 0);
13973         }
13974         continue;
13975     }
13976     /* End of additions by HGM */
13977
13978     /* unknown feature: complain and skip */
13979     q = p;
13980     while (*q && *q != '=') q++;
13981     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13982     SendToProgram(buf, cps);
13983     p = q;
13984     if (*p == '=') {
13985       p++;
13986       if (*p == '\"') {
13987         p++;
13988         while (*p && *p != '\"') p++;
13989         if (*p == '\"') p++;
13990       } else {
13991         while (*p && *p != ' ') p++;
13992       }
13993     }
13994   }
13995
13996 }
13997
13998 void
13999 PeriodicUpdatesEvent(newState)
14000      int newState;
14001 {
14002     if (newState == appData.periodicUpdates)
14003       return;
14004
14005     appData.periodicUpdates=newState;
14006
14007     /* Display type changes, so update it now */
14008 //    DisplayAnalysis();
14009
14010     /* Get the ball rolling again... */
14011     if (newState) {
14012         AnalysisPeriodicEvent(1);
14013         StartAnalysisClock();
14014     }
14015 }
14016
14017 void
14018 PonderNextMoveEvent(newState)
14019      int newState;
14020 {
14021     if (newState == appData.ponderNextMove) return;
14022     if (gameMode == EditPosition) EditPositionDone(TRUE);
14023     if (newState) {
14024         SendToProgram("hard\n", &first);
14025         if (gameMode == TwoMachinesPlay) {
14026             SendToProgram("hard\n", &second);
14027         }
14028     } else {
14029         SendToProgram("easy\n", &first);
14030         thinkOutput[0] = NULLCHAR;
14031         if (gameMode == TwoMachinesPlay) {
14032             SendToProgram("easy\n", &second);
14033         }
14034     }
14035     appData.ponderNextMove = newState;
14036 }
14037
14038 void
14039 NewSettingEvent(option, feature, command, value)
14040      char *command;
14041      int option, value, *feature;
14042 {
14043     char buf[MSG_SIZ];
14044
14045     if (gameMode == EditPosition) EditPositionDone(TRUE);
14046     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14047     if(feature == NULL || *feature) SendToProgram(buf, &first);
14048     if (gameMode == TwoMachinesPlay) {
14049         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14050     }
14051 }
14052
14053 void
14054 ShowThinkingEvent()
14055 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14056 {
14057     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14058     int newState = appData.showThinking
14059         // [HGM] thinking: other features now need thinking output as well
14060         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14061
14062     if (oldState == newState) return;
14063     oldState = newState;
14064     if (gameMode == EditPosition) EditPositionDone(TRUE);
14065     if (oldState) {
14066         SendToProgram("post\n", &first);
14067         if (gameMode == TwoMachinesPlay) {
14068             SendToProgram("post\n", &second);
14069         }
14070     } else {
14071         SendToProgram("nopost\n", &first);
14072         thinkOutput[0] = NULLCHAR;
14073         if (gameMode == TwoMachinesPlay) {
14074             SendToProgram("nopost\n", &second);
14075         }
14076     }
14077 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14078 }
14079
14080 void
14081 AskQuestionEvent(title, question, replyPrefix, which)
14082      char *title; char *question; char *replyPrefix; char *which;
14083 {
14084   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14085   if (pr == NoProc) return;
14086   AskQuestion(title, question, replyPrefix, pr);
14087 }
14088
14089 void
14090 DisplayMove(moveNumber)
14091      int moveNumber;
14092 {
14093     char message[MSG_SIZ];
14094     char res[MSG_SIZ];
14095     char cpThinkOutput[MSG_SIZ];
14096
14097     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14098
14099     if (moveNumber == forwardMostMove - 1 ||
14100         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14101
14102         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14103
14104         if (strchr(cpThinkOutput, '\n')) {
14105             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14106         }
14107     } else {
14108         *cpThinkOutput = NULLCHAR;
14109     }
14110
14111     /* [AS] Hide thinking from human user */
14112     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14113         *cpThinkOutput = NULLCHAR;
14114         if( thinkOutput[0] != NULLCHAR ) {
14115             int i;
14116
14117             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14118                 cpThinkOutput[i] = '.';
14119             }
14120             cpThinkOutput[i] = NULLCHAR;
14121             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14122         }
14123     }
14124
14125     if (moveNumber == forwardMostMove - 1 &&
14126         gameInfo.resultDetails != NULL) {
14127         if (gameInfo.resultDetails[0] == NULLCHAR) {
14128           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14129         } else {
14130           snprintf(res, MSG_SIZ, " {%s} %s",
14131                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14132         }
14133     } else {
14134         res[0] = NULLCHAR;
14135     }
14136
14137     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14138         DisplayMessage(res, cpThinkOutput);
14139     } else {
14140       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14141                 WhiteOnMove(moveNumber) ? " " : ".. ",
14142                 parseList[moveNumber], res);
14143         DisplayMessage(message, cpThinkOutput);
14144     }
14145 }
14146
14147 void
14148 DisplayComment(moveNumber, text)
14149      int moveNumber;
14150      char *text;
14151 {
14152     char title[MSG_SIZ];
14153     char buf[8000]; // comment can be long!
14154     int score, depth;
14155
14156     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14157       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14158     } else {
14159       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14160               WhiteOnMove(moveNumber) ? " " : ".. ",
14161               parseList[moveNumber]);
14162     }
14163     // [HGM] PV info: display PV info together with (or as) comment
14164     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14165       if(text == NULL) text = "";
14166       score = pvInfoList[moveNumber].score;
14167       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14168               depth, (pvInfoList[moveNumber].time+50)/100, text);
14169       text = buf;
14170     }
14171     if (text != NULL && (appData.autoDisplayComment || commentUp))
14172         CommentPopUp(title, text);
14173 }
14174
14175 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14176  * might be busy thinking or pondering.  It can be omitted if your
14177  * gnuchess is configured to stop thinking immediately on any user
14178  * input.  However, that gnuchess feature depends on the FIONREAD
14179  * ioctl, which does not work properly on some flavors of Unix.
14180  */
14181 void
14182 Attention(cps)
14183      ChessProgramState *cps;
14184 {
14185 #if ATTENTION
14186     if (!cps->useSigint) return;
14187     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14188     switch (gameMode) {
14189       case MachinePlaysWhite:
14190       case MachinePlaysBlack:
14191       case TwoMachinesPlay:
14192       case IcsPlayingWhite:
14193       case IcsPlayingBlack:
14194       case AnalyzeMode:
14195       case AnalyzeFile:
14196         /* Skip if we know it isn't thinking */
14197         if (!cps->maybeThinking) return;
14198         if (appData.debugMode)
14199           fprintf(debugFP, "Interrupting %s\n", cps->which);
14200         InterruptChildProcess(cps->pr);
14201         cps->maybeThinking = FALSE;
14202         break;
14203       default:
14204         break;
14205     }
14206 #endif /*ATTENTION*/
14207 }
14208
14209 int
14210 CheckFlags()
14211 {
14212     if (whiteTimeRemaining <= 0) {
14213         if (!whiteFlag) {
14214             whiteFlag = TRUE;
14215             if (appData.icsActive) {
14216                 if (appData.autoCallFlag &&
14217                     gameMode == IcsPlayingBlack && !blackFlag) {
14218                   SendToICS(ics_prefix);
14219                   SendToICS("flag\n");
14220                 }
14221             } else {
14222                 if (blackFlag) {
14223                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14224                 } else {
14225                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14226                     if (appData.autoCallFlag) {
14227                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14228                         return TRUE;
14229                     }
14230                 }
14231             }
14232         }
14233     }
14234     if (blackTimeRemaining <= 0) {
14235         if (!blackFlag) {
14236             blackFlag = TRUE;
14237             if (appData.icsActive) {
14238                 if (appData.autoCallFlag &&
14239                     gameMode == IcsPlayingWhite && !whiteFlag) {
14240                   SendToICS(ics_prefix);
14241                   SendToICS("flag\n");
14242                 }
14243             } else {
14244                 if (whiteFlag) {
14245                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14246                 } else {
14247                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14248                     if (appData.autoCallFlag) {
14249                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14250                         return TRUE;
14251                     }
14252                 }
14253             }
14254         }
14255     }
14256     return FALSE;
14257 }
14258
14259 void
14260 CheckTimeControl()
14261 {
14262     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14263         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14264
14265     /*
14266      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14267      */
14268     if ( !WhiteOnMove(forwardMostMove) ) {
14269         /* White made time control */
14270         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14271         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14272         /* [HGM] time odds: correct new time quota for time odds! */
14273                                             / WhitePlayer()->timeOdds;
14274         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14275     } else {
14276         lastBlack -= blackTimeRemaining;
14277         /* Black made time control */
14278         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14279                                             / WhitePlayer()->other->timeOdds;
14280         lastWhite = whiteTimeRemaining;
14281     }
14282 }
14283
14284 void
14285 DisplayBothClocks()
14286 {
14287     int wom = gameMode == EditPosition ?
14288       !blackPlaysFirst : WhiteOnMove(currentMove);
14289     DisplayWhiteClock(whiteTimeRemaining, wom);
14290     DisplayBlackClock(blackTimeRemaining, !wom);
14291 }
14292
14293
14294 /* Timekeeping seems to be a portability nightmare.  I think everyone
14295    has ftime(), but I'm really not sure, so I'm including some ifdefs
14296    to use other calls if you don't.  Clocks will be less accurate if
14297    you have neither ftime nor gettimeofday.
14298 */
14299
14300 /* VS 2008 requires the #include outside of the function */
14301 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14302 #include <sys/timeb.h>
14303 #endif
14304
14305 /* Get the current time as a TimeMark */
14306 void
14307 GetTimeMark(tm)
14308      TimeMark *tm;
14309 {
14310 #if HAVE_GETTIMEOFDAY
14311
14312     struct timeval timeVal;
14313     struct timezone timeZone;
14314
14315     gettimeofday(&timeVal, &timeZone);
14316     tm->sec = (long) timeVal.tv_sec;
14317     tm->ms = (int) (timeVal.tv_usec / 1000L);
14318
14319 #else /*!HAVE_GETTIMEOFDAY*/
14320 #if HAVE_FTIME
14321
14322 // include <sys/timeb.h> / moved to just above start of function
14323     struct timeb timeB;
14324
14325     ftime(&timeB);
14326     tm->sec = (long) timeB.time;
14327     tm->ms = (int) timeB.millitm;
14328
14329 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14330     tm->sec = (long) time(NULL);
14331     tm->ms = 0;
14332 #endif
14333 #endif
14334 }
14335
14336 /* Return the difference in milliseconds between two
14337    time marks.  We assume the difference will fit in a long!
14338 */
14339 long
14340 SubtractTimeMarks(tm2, tm1)
14341      TimeMark *tm2, *tm1;
14342 {
14343     return 1000L*(tm2->sec - tm1->sec) +
14344            (long) (tm2->ms - tm1->ms);
14345 }
14346
14347
14348 /*
14349  * Code to manage the game clocks.
14350  *
14351  * In tournament play, black starts the clock and then white makes a move.
14352  * We give the human user a slight advantage if he is playing white---the
14353  * clocks don't run until he makes his first move, so it takes zero time.
14354  * Also, we don't account for network lag, so we could get out of sync
14355  * with GNU Chess's clock -- but then, referees are always right.
14356  */
14357
14358 static TimeMark tickStartTM;
14359 static long intendedTickLength;
14360
14361 long
14362 NextTickLength(timeRemaining)
14363      long timeRemaining;
14364 {
14365     long nominalTickLength, nextTickLength;
14366
14367     if (timeRemaining > 0L && timeRemaining <= 10000L)
14368       nominalTickLength = 100L;
14369     else
14370       nominalTickLength = 1000L;
14371     nextTickLength = timeRemaining % nominalTickLength;
14372     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14373
14374     return nextTickLength;
14375 }
14376
14377 /* Adjust clock one minute up or down */
14378 void
14379 AdjustClock(Boolean which, int dir)
14380 {
14381     if(which) blackTimeRemaining += 60000*dir;
14382     else      whiteTimeRemaining += 60000*dir;
14383     DisplayBothClocks();
14384 }
14385
14386 /* Stop clocks and reset to a fresh time control */
14387 void
14388 ResetClocks()
14389 {
14390     (void) StopClockTimer();
14391     if (appData.icsActive) {
14392         whiteTimeRemaining = blackTimeRemaining = 0;
14393     } else if (searchTime) {
14394         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14395         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14396     } else { /* [HGM] correct new time quote for time odds */
14397         whiteTC = blackTC = fullTimeControlString;
14398         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14399         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14400     }
14401     if (whiteFlag || blackFlag) {
14402         DisplayTitle("");
14403         whiteFlag = blackFlag = FALSE;
14404     }
14405     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14406     DisplayBothClocks();
14407 }
14408
14409 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14410
14411 /* Decrement running clock by amount of time that has passed */
14412 void
14413 DecrementClocks()
14414 {
14415     long timeRemaining;
14416     long lastTickLength, fudge;
14417     TimeMark now;
14418
14419     if (!appData.clockMode) return;
14420     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14421
14422     GetTimeMark(&now);
14423
14424     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14425
14426     /* Fudge if we woke up a little too soon */
14427     fudge = intendedTickLength - lastTickLength;
14428     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14429
14430     if (WhiteOnMove(forwardMostMove)) {
14431         if(whiteNPS >= 0) lastTickLength = 0;
14432         timeRemaining = whiteTimeRemaining -= lastTickLength;
14433         if(timeRemaining < 0 && !appData.icsActive) {
14434             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14435             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14436                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14437                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14438             }
14439         }
14440         DisplayWhiteClock(whiteTimeRemaining - fudge,
14441                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14442     } else {
14443         if(blackNPS >= 0) lastTickLength = 0;
14444         timeRemaining = blackTimeRemaining -= lastTickLength;
14445         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14446             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14447             if(suddenDeath) {
14448                 blackStartMove = forwardMostMove;
14449                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14450             }
14451         }
14452         DisplayBlackClock(blackTimeRemaining - fudge,
14453                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14454     }
14455     if (CheckFlags()) return;
14456
14457     tickStartTM = now;
14458     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14459     StartClockTimer(intendedTickLength);
14460
14461     /* if the time remaining has fallen below the alarm threshold, sound the
14462      * alarm. if the alarm has sounded and (due to a takeback or time control
14463      * with increment) the time remaining has increased to a level above the
14464      * threshold, reset the alarm so it can sound again.
14465      */
14466
14467     if (appData.icsActive && appData.icsAlarm) {
14468
14469         /* make sure we are dealing with the user's clock */
14470         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14471                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14472            )) return;
14473
14474         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14475             alarmSounded = FALSE;
14476         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14477             PlayAlarmSound();
14478             alarmSounded = TRUE;
14479         }
14480     }
14481 }
14482
14483
14484 /* A player has just moved, so stop the previously running
14485    clock and (if in clock mode) start the other one.
14486    We redisplay both clocks in case we're in ICS mode, because
14487    ICS gives us an update to both clocks after every move.
14488    Note that this routine is called *after* forwardMostMove
14489    is updated, so the last fractional tick must be subtracted
14490    from the color that is *not* on move now.
14491 */
14492 void
14493 SwitchClocks(int newMoveNr)
14494 {
14495     long lastTickLength;
14496     TimeMark now;
14497     int flagged = FALSE;
14498
14499     GetTimeMark(&now);
14500
14501     if (StopClockTimer() && appData.clockMode) {
14502         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14503         if (!WhiteOnMove(forwardMostMove)) {
14504             if(blackNPS >= 0) lastTickLength = 0;
14505             blackTimeRemaining -= lastTickLength;
14506            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14507 //         if(pvInfoList[forwardMostMove-1].time == -1)
14508                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14509                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14510         } else {
14511            if(whiteNPS >= 0) lastTickLength = 0;
14512            whiteTimeRemaining -= lastTickLength;
14513            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14514 //         if(pvInfoList[forwardMostMove-1].time == -1)
14515                  pvInfoList[forwardMostMove-1].time =
14516                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14517         }
14518         flagged = CheckFlags();
14519     }
14520     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14521     CheckTimeControl();
14522
14523     if (flagged || !appData.clockMode) return;
14524
14525     switch (gameMode) {
14526       case MachinePlaysBlack:
14527       case MachinePlaysWhite:
14528       case BeginningOfGame:
14529         if (pausing) return;
14530         break;
14531
14532       case EditGame:
14533       case PlayFromGameFile:
14534       case IcsExamining:
14535         return;
14536
14537       default:
14538         break;
14539     }
14540
14541     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14542         if(WhiteOnMove(forwardMostMove))
14543              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14544         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14545     }
14546
14547     tickStartTM = now;
14548     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14549       whiteTimeRemaining : blackTimeRemaining);
14550     StartClockTimer(intendedTickLength);
14551 }
14552
14553
14554 /* Stop both clocks */
14555 void
14556 StopClocks()
14557 {
14558     long lastTickLength;
14559     TimeMark now;
14560
14561     if (!StopClockTimer()) return;
14562     if (!appData.clockMode) return;
14563
14564     GetTimeMark(&now);
14565
14566     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14567     if (WhiteOnMove(forwardMostMove)) {
14568         if(whiteNPS >= 0) lastTickLength = 0;
14569         whiteTimeRemaining -= lastTickLength;
14570         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14571     } else {
14572         if(blackNPS >= 0) lastTickLength = 0;
14573         blackTimeRemaining -= lastTickLength;
14574         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14575     }
14576     CheckFlags();
14577 }
14578
14579 /* Start clock of player on move.  Time may have been reset, so
14580    if clock is already running, stop and restart it. */
14581 void
14582 StartClocks()
14583 {
14584     (void) StopClockTimer(); /* in case it was running already */
14585     DisplayBothClocks();
14586     if (CheckFlags()) return;
14587
14588     if (!appData.clockMode) return;
14589     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14590
14591     GetTimeMark(&tickStartTM);
14592     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14593       whiteTimeRemaining : blackTimeRemaining);
14594
14595    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14596     whiteNPS = blackNPS = -1;
14597     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14598        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14599         whiteNPS = first.nps;
14600     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14601        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14602         blackNPS = first.nps;
14603     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14604         whiteNPS = second.nps;
14605     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14606         blackNPS = second.nps;
14607     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14608
14609     StartClockTimer(intendedTickLength);
14610 }
14611
14612 char *
14613 TimeString(ms)
14614      long ms;
14615 {
14616     long second, minute, hour, day;
14617     char *sign = "";
14618     static char buf[32];
14619
14620     if (ms > 0 && ms <= 9900) {
14621       /* convert milliseconds to tenths, rounding up */
14622       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14623
14624       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14625       return buf;
14626     }
14627
14628     /* convert milliseconds to seconds, rounding up */
14629     /* use floating point to avoid strangeness of integer division
14630        with negative dividends on many machines */
14631     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14632
14633     if (second < 0) {
14634         sign = "-";
14635         second = -second;
14636     }
14637
14638     day = second / (60 * 60 * 24);
14639     second = second % (60 * 60 * 24);
14640     hour = second / (60 * 60);
14641     second = second % (60 * 60);
14642     minute = second / 60;
14643     second = second % 60;
14644
14645     if (day > 0)
14646       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14647               sign, day, hour, minute, second);
14648     else if (hour > 0)
14649       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14650     else
14651       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14652
14653     return buf;
14654 }
14655
14656
14657 /*
14658  * This is necessary because some C libraries aren't ANSI C compliant yet.
14659  */
14660 char *
14661 StrStr(string, match)
14662      char *string, *match;
14663 {
14664     int i, length;
14665
14666     length = strlen(match);
14667
14668     for (i = strlen(string) - length; i >= 0; i--, string++)
14669       if (!strncmp(match, string, length))
14670         return string;
14671
14672     return NULL;
14673 }
14674
14675 char *
14676 StrCaseStr(string, match)
14677      char *string, *match;
14678 {
14679     int i, j, length;
14680
14681     length = strlen(match);
14682
14683     for (i = strlen(string) - length; i >= 0; i--, string++) {
14684         for (j = 0; j < length; j++) {
14685             if (ToLower(match[j]) != ToLower(string[j]))
14686               break;
14687         }
14688         if (j == length) return string;
14689     }
14690
14691     return NULL;
14692 }
14693
14694 #ifndef _amigados
14695 int
14696 StrCaseCmp(s1, s2)
14697      char *s1, *s2;
14698 {
14699     char c1, c2;
14700
14701     for (;;) {
14702         c1 = ToLower(*s1++);
14703         c2 = ToLower(*s2++);
14704         if (c1 > c2) return 1;
14705         if (c1 < c2) return -1;
14706         if (c1 == NULLCHAR) return 0;
14707     }
14708 }
14709
14710
14711 int
14712 ToLower(c)
14713      int c;
14714 {
14715     return isupper(c) ? tolower(c) : c;
14716 }
14717
14718
14719 int
14720 ToUpper(c)
14721      int c;
14722 {
14723     return islower(c) ? toupper(c) : c;
14724 }
14725 #endif /* !_amigados    */
14726
14727 char *
14728 StrSave(s)
14729      char *s;
14730 {
14731   char *ret;
14732
14733   if ((ret = (char *) malloc(strlen(s) + 1)))
14734     {
14735       safeStrCpy(ret, s, strlen(s)+1);
14736     }
14737   return ret;
14738 }
14739
14740 char *
14741 StrSavePtr(s, savePtr)
14742      char *s, **savePtr;
14743 {
14744     if (*savePtr) {
14745         free(*savePtr);
14746     }
14747     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14748       safeStrCpy(*savePtr, s, strlen(s)+1);
14749     }
14750     return(*savePtr);
14751 }
14752
14753 char *
14754 PGNDate()
14755 {
14756     time_t clock;
14757     struct tm *tm;
14758     char buf[MSG_SIZ];
14759
14760     clock = time((time_t *)NULL);
14761     tm = localtime(&clock);
14762     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14763             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14764     return StrSave(buf);
14765 }
14766
14767
14768 char *
14769 PositionToFEN(move, overrideCastling)
14770      int move;
14771      char *overrideCastling;
14772 {
14773     int i, j, fromX, fromY, toX, toY;
14774     int whiteToPlay;
14775     char buf[128];
14776     char *p, *q;
14777     int emptycount;
14778     ChessSquare piece;
14779
14780     whiteToPlay = (gameMode == EditPosition) ?
14781       !blackPlaysFirst : (move % 2 == 0);
14782     p = buf;
14783
14784     /* Piece placement data */
14785     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14786         emptycount = 0;
14787         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14788             if (boards[move][i][j] == EmptySquare) {
14789                 emptycount++;
14790             } else { ChessSquare piece = boards[move][i][j];
14791                 if (emptycount > 0) {
14792                     if(emptycount<10) /* [HGM] can be >= 10 */
14793                         *p++ = '0' + emptycount;
14794                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14795                     emptycount = 0;
14796                 }
14797                 if(PieceToChar(piece) == '+') {
14798                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14799                     *p++ = '+';
14800                     piece = (ChessSquare)(DEMOTED piece);
14801                 }
14802                 *p++ = PieceToChar(piece);
14803                 if(p[-1] == '~') {
14804                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14805                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14806                     *p++ = '~';
14807                 }
14808             }
14809         }
14810         if (emptycount > 0) {
14811             if(emptycount<10) /* [HGM] can be >= 10 */
14812                 *p++ = '0' + emptycount;
14813             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14814             emptycount = 0;
14815         }
14816         *p++ = '/';
14817     }
14818     *(p - 1) = ' ';
14819
14820     /* [HGM] print Crazyhouse or Shogi holdings */
14821     if( gameInfo.holdingsWidth ) {
14822         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14823         q = p;
14824         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14825             piece = boards[move][i][BOARD_WIDTH-1];
14826             if( piece != EmptySquare )
14827               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14828                   *p++ = PieceToChar(piece);
14829         }
14830         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14831             piece = boards[move][BOARD_HEIGHT-i-1][0];
14832             if( piece != EmptySquare )
14833               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14834                   *p++ = PieceToChar(piece);
14835         }
14836
14837         if( q == p ) *p++ = '-';
14838         *p++ = ']';
14839         *p++ = ' ';
14840     }
14841
14842     /* Active color */
14843     *p++ = whiteToPlay ? 'w' : 'b';
14844     *p++ = ' ';
14845
14846   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14847     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14848   } else {
14849   if(nrCastlingRights) {
14850      q = p;
14851      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14852        /* [HGM] write directly from rights */
14853            if(boards[move][CASTLING][2] != NoRights &&
14854               boards[move][CASTLING][0] != NoRights   )
14855                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14856            if(boards[move][CASTLING][2] != NoRights &&
14857               boards[move][CASTLING][1] != NoRights   )
14858                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14859            if(boards[move][CASTLING][5] != NoRights &&
14860               boards[move][CASTLING][3] != NoRights   )
14861                 *p++ = boards[move][CASTLING][3] + AAA;
14862            if(boards[move][CASTLING][5] != NoRights &&
14863               boards[move][CASTLING][4] != NoRights   )
14864                 *p++ = boards[move][CASTLING][4] + AAA;
14865      } else {
14866
14867         /* [HGM] write true castling rights */
14868         if( nrCastlingRights == 6 ) {
14869             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14870                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14871             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14872                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14873             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14874                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14875             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14876                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14877         }
14878      }
14879      if (q == p) *p++ = '-'; /* No castling rights */
14880      *p++ = ' ';
14881   }
14882
14883   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14884      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14885     /* En passant target square */
14886     if (move > backwardMostMove) {
14887         fromX = moveList[move - 1][0] - AAA;
14888         fromY = moveList[move - 1][1] - ONE;
14889         toX = moveList[move - 1][2] - AAA;
14890         toY = moveList[move - 1][3] - ONE;
14891         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14892             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14893             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14894             fromX == toX) {
14895             /* 2-square pawn move just happened */
14896             *p++ = toX + AAA;
14897             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14898         } else {
14899             *p++ = '-';
14900         }
14901     } else if(move == backwardMostMove) {
14902         // [HGM] perhaps we should always do it like this, and forget the above?
14903         if((signed char)boards[move][EP_STATUS] >= 0) {
14904             *p++ = boards[move][EP_STATUS] + AAA;
14905             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14906         } else {
14907             *p++ = '-';
14908         }
14909     } else {
14910         *p++ = '-';
14911     }
14912     *p++ = ' ';
14913   }
14914   }
14915
14916     /* [HGM] find reversible plies */
14917     {   int i = 0, j=move;
14918
14919         if (appData.debugMode) { int k;
14920             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14921             for(k=backwardMostMove; k<=forwardMostMove; k++)
14922                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14923
14924         }
14925
14926         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14927         if( j == backwardMostMove ) i += initialRulePlies;
14928         sprintf(p, "%d ", i);
14929         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14930     }
14931     /* Fullmove number */
14932     sprintf(p, "%d", (move / 2) + 1);
14933
14934     return StrSave(buf);
14935 }
14936
14937 Boolean
14938 ParseFEN(board, blackPlaysFirst, fen)
14939     Board board;
14940      int *blackPlaysFirst;
14941      char *fen;
14942 {
14943     int i, j;
14944     char *p, c;
14945     int emptycount;
14946     ChessSquare piece;
14947
14948     p = fen;
14949
14950     /* [HGM] by default clear Crazyhouse holdings, if present */
14951     if(gameInfo.holdingsWidth) {
14952        for(i=0; i<BOARD_HEIGHT; i++) {
14953            board[i][0]             = EmptySquare; /* black holdings */
14954            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14955            board[i][1]             = (ChessSquare) 0; /* black counts */
14956            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14957        }
14958     }
14959
14960     /* Piece placement data */
14961     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14962         j = 0;
14963         for (;;) {
14964             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14965                 if (*p == '/') p++;
14966                 emptycount = gameInfo.boardWidth - j;
14967                 while (emptycount--)
14968                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14969                 break;
14970 #if(BOARD_FILES >= 10)
14971             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14972                 p++; emptycount=10;
14973                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14974                 while (emptycount--)
14975                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14976 #endif
14977             } else if (isdigit(*p)) {
14978                 emptycount = *p++ - '0';
14979                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14980                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14981                 while (emptycount--)
14982                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14983             } else if (*p == '+' || isalpha(*p)) {
14984                 if (j >= gameInfo.boardWidth) return FALSE;
14985                 if(*p=='+') {
14986                     piece = CharToPiece(*++p);
14987                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14988                     piece = (ChessSquare) (PROMOTED piece ); p++;
14989                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14990                 } else piece = CharToPiece(*p++);
14991
14992                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14993                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14994                     piece = (ChessSquare) (PROMOTED piece);
14995                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14996                     p++;
14997                 }
14998                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14999             } else {
15000                 return FALSE;
15001             }
15002         }
15003     }
15004     while (*p == '/' || *p == ' ') p++;
15005
15006     /* [HGM] look for Crazyhouse holdings here */
15007     while(*p==' ') p++;
15008     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15009         if(*p == '[') p++;
15010         if(*p == '-' ) p++; /* empty holdings */ else {
15011             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15012             /* if we would allow FEN reading to set board size, we would   */
15013             /* have to add holdings and shift the board read so far here   */
15014             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15015                 p++;
15016                 if((int) piece >= (int) BlackPawn ) {
15017                     i = (int)piece - (int)BlackPawn;
15018                     i = PieceToNumber((ChessSquare)i);
15019                     if( i >= gameInfo.holdingsSize ) return FALSE;
15020                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15021                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15022                 } else {
15023                     i = (int)piece - (int)WhitePawn;
15024                     i = PieceToNumber((ChessSquare)i);
15025                     if( i >= gameInfo.holdingsSize ) return FALSE;
15026                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15027                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15028                 }
15029             }
15030         }
15031         if(*p == ']') p++;
15032     }
15033
15034     while(*p == ' ') p++;
15035
15036     /* Active color */
15037     c = *p++;
15038     if(appData.colorNickNames) {
15039       if( c == appData.colorNickNames[0] ) c = 'w'; else
15040       if( c == appData.colorNickNames[1] ) c = 'b';
15041     }
15042     switch (c) {
15043       case 'w':
15044         *blackPlaysFirst = FALSE;
15045         break;
15046       case 'b':
15047         *blackPlaysFirst = TRUE;
15048         break;
15049       default:
15050         return FALSE;
15051     }
15052
15053     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15054     /* return the extra info in global variiables             */
15055
15056     /* set defaults in case FEN is incomplete */
15057     board[EP_STATUS] = EP_UNKNOWN;
15058     for(i=0; i<nrCastlingRights; i++ ) {
15059         board[CASTLING][i] =
15060             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15061     }   /* assume possible unless obviously impossible */
15062     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15063     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15064     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15065                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15066     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15067     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15068     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15069                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15070     FENrulePlies = 0;
15071
15072     while(*p==' ') p++;
15073     if(nrCastlingRights) {
15074       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15075           /* castling indicator present, so default becomes no castlings */
15076           for(i=0; i<nrCastlingRights; i++ ) {
15077                  board[CASTLING][i] = NoRights;
15078           }
15079       }
15080       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15081              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15082              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15083              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15084         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15085
15086         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15087             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15088             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15089         }
15090         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15091             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15092         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15093                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15094         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15095                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15096         switch(c) {
15097           case'K':
15098               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15099               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15100               board[CASTLING][2] = whiteKingFile;
15101               break;
15102           case'Q':
15103               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15104               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15105               board[CASTLING][2] = whiteKingFile;
15106               break;
15107           case'k':
15108               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15109               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15110               board[CASTLING][5] = blackKingFile;
15111               break;
15112           case'q':
15113               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15114               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15115               board[CASTLING][5] = blackKingFile;
15116           case '-':
15117               break;
15118           default: /* FRC castlings */
15119               if(c >= 'a') { /* black rights */
15120                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15121                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15122                   if(i == BOARD_RGHT) break;
15123                   board[CASTLING][5] = i;
15124                   c -= AAA;
15125                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15126                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15127                   if(c > i)
15128                       board[CASTLING][3] = c;
15129                   else
15130                       board[CASTLING][4] = c;
15131               } else { /* white rights */
15132                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15133                     if(board[0][i] == WhiteKing) break;
15134                   if(i == BOARD_RGHT) break;
15135                   board[CASTLING][2] = i;
15136                   c -= AAA - 'a' + 'A';
15137                   if(board[0][c] >= WhiteKing) break;
15138                   if(c > i)
15139                       board[CASTLING][0] = c;
15140                   else
15141                       board[CASTLING][1] = c;
15142               }
15143         }
15144       }
15145       for(i=0; i<nrCastlingRights; i++)
15146         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15147     if (appData.debugMode) {
15148         fprintf(debugFP, "FEN castling rights:");
15149         for(i=0; i<nrCastlingRights; i++)
15150         fprintf(debugFP, " %d", board[CASTLING][i]);
15151         fprintf(debugFP, "\n");
15152     }
15153
15154       while(*p==' ') p++;
15155     }
15156
15157     /* read e.p. field in games that know e.p. capture */
15158     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15159        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15160       if(*p=='-') {
15161         p++; board[EP_STATUS] = EP_NONE;
15162       } else {
15163          char c = *p++ - AAA;
15164
15165          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15166          if(*p >= '0' && *p <='9') p++;
15167          board[EP_STATUS] = c;
15168       }
15169     }
15170
15171
15172     if(sscanf(p, "%d", &i) == 1) {
15173         FENrulePlies = i; /* 50-move ply counter */
15174         /* (The move number is still ignored)    */
15175     }
15176
15177     return TRUE;
15178 }
15179
15180 void
15181 EditPositionPasteFEN(char *fen)
15182 {
15183   if (fen != NULL) {
15184     Board initial_position;
15185
15186     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15187       DisplayError(_("Bad FEN position in clipboard"), 0);
15188       return ;
15189     } else {
15190       int savedBlackPlaysFirst = blackPlaysFirst;
15191       EditPositionEvent();
15192       blackPlaysFirst = savedBlackPlaysFirst;
15193       CopyBoard(boards[0], initial_position);
15194       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15195       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15196       DisplayBothClocks();
15197       DrawPosition(FALSE, boards[currentMove]);
15198     }
15199   }
15200 }
15201
15202 static char cseq[12] = "\\   ";
15203
15204 Boolean set_cont_sequence(char *new_seq)
15205 {
15206     int len;
15207     Boolean ret;
15208
15209     // handle bad attempts to set the sequence
15210         if (!new_seq)
15211                 return 0; // acceptable error - no debug
15212
15213     len = strlen(new_seq);
15214     ret = (len > 0) && (len < sizeof(cseq));
15215     if (ret)
15216       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15217     else if (appData.debugMode)
15218       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15219     return ret;
15220 }
15221
15222 /*
15223     reformat a source message so words don't cross the width boundary.  internal
15224     newlines are not removed.  returns the wrapped size (no null character unless
15225     included in source message).  If dest is NULL, only calculate the size required
15226     for the dest buffer.  lp argument indicats line position upon entry, and it's
15227     passed back upon exit.
15228 */
15229 int wrap(char *dest, char *src, int count, int width, int *lp)
15230 {
15231     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15232
15233     cseq_len = strlen(cseq);
15234     old_line = line = *lp;
15235     ansi = len = clen = 0;
15236
15237     for (i=0; i < count; i++)
15238     {
15239         if (src[i] == '\033')
15240             ansi = 1;
15241
15242         // if we hit the width, back up
15243         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15244         {
15245             // store i & len in case the word is too long
15246             old_i = i, old_len = len;
15247
15248             // find the end of the last word
15249             while (i && src[i] != ' ' && src[i] != '\n')
15250             {
15251                 i--;
15252                 len--;
15253             }
15254
15255             // word too long?  restore i & len before splitting it
15256             if ((old_i-i+clen) >= width)
15257             {
15258                 i = old_i;
15259                 len = old_len;
15260             }
15261
15262             // extra space?
15263             if (i && src[i-1] == ' ')
15264                 len--;
15265
15266             if (src[i] != ' ' && src[i] != '\n')
15267             {
15268                 i--;
15269                 if (len)
15270                     len--;
15271             }
15272
15273             // now append the newline and continuation sequence
15274             if (dest)
15275                 dest[len] = '\n';
15276             len++;
15277             if (dest)
15278                 strncpy(dest+len, cseq, cseq_len);
15279             len += cseq_len;
15280             line = cseq_len;
15281             clen = cseq_len;
15282             continue;
15283         }
15284
15285         if (dest)
15286             dest[len] = src[i];
15287         len++;
15288         if (!ansi)
15289             line++;
15290         if (src[i] == '\n')
15291             line = 0;
15292         if (src[i] == 'm')
15293             ansi = 0;
15294     }
15295     if (dest && appData.debugMode)
15296     {
15297         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15298             count, width, line, len, *lp);
15299         show_bytes(debugFP, src, count);
15300         fprintf(debugFP, "\ndest: ");
15301         show_bytes(debugFP, dest, len);
15302         fprintf(debugFP, "\n");
15303     }
15304     *lp = dest ? line : old_line;
15305
15306     return len;
15307 }
15308
15309 // [HGM] vari: routines for shelving variations
15310
15311 void
15312 PushTail(int firstMove, int lastMove)
15313 {
15314         int i, j, nrMoves = lastMove - firstMove;
15315
15316         if(appData.icsActive) { // only in local mode
15317                 forwardMostMove = currentMove; // mimic old ICS behavior
15318                 return;
15319         }
15320         if(storedGames >= MAX_VARIATIONS-1) return;
15321
15322         // push current tail of game on stack
15323         savedResult[storedGames] = gameInfo.result;
15324         savedDetails[storedGames] = gameInfo.resultDetails;
15325         gameInfo.resultDetails = NULL;
15326         savedFirst[storedGames] = firstMove;
15327         savedLast [storedGames] = lastMove;
15328         savedFramePtr[storedGames] = framePtr;
15329         framePtr -= nrMoves; // reserve space for the boards
15330         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15331             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15332             for(j=0; j<MOVE_LEN; j++)
15333                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15334             for(j=0; j<2*MOVE_LEN; j++)
15335                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15336             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15337             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15338             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15339             pvInfoList[firstMove+i-1].depth = 0;
15340             commentList[framePtr+i] = commentList[firstMove+i];
15341             commentList[firstMove+i] = NULL;
15342         }
15343
15344         storedGames++;
15345         forwardMostMove = firstMove; // truncate game so we can start variation
15346         if(storedGames == 1) GreyRevert(FALSE);
15347 }
15348
15349 Boolean
15350 PopTail(Boolean annotate)
15351 {
15352         int i, j, nrMoves;
15353         char buf[8000], moveBuf[20];
15354
15355         if(appData.icsActive) return FALSE; // only in local mode
15356         if(!storedGames) return FALSE; // sanity
15357         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15358
15359         storedGames--;
15360         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15361         nrMoves = savedLast[storedGames] - currentMove;
15362         if(annotate) {
15363                 int cnt = 10;
15364                 if(!WhiteOnMove(currentMove))
15365                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15366                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15367                 for(i=currentMove; i<forwardMostMove; i++) {
15368                         if(WhiteOnMove(i))
15369                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15370                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15371                         strcat(buf, moveBuf);
15372                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15373                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15374                 }
15375                 strcat(buf, ")");
15376         }
15377         for(i=1; i<=nrMoves; i++) { // copy last variation back
15378             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15379             for(j=0; j<MOVE_LEN; j++)
15380                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15381             for(j=0; j<2*MOVE_LEN; j++)
15382                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15383             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15384             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15385             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15386             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15387             commentList[currentMove+i] = commentList[framePtr+i];
15388             commentList[framePtr+i] = NULL;
15389         }
15390         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15391         framePtr = savedFramePtr[storedGames];
15392         gameInfo.result = savedResult[storedGames];
15393         if(gameInfo.resultDetails != NULL) {
15394             free(gameInfo.resultDetails);
15395       }
15396         gameInfo.resultDetails = savedDetails[storedGames];
15397         forwardMostMove = currentMove + nrMoves;
15398         if(storedGames == 0) GreyRevert(TRUE);
15399         return TRUE;
15400 }
15401
15402 void
15403 CleanupTail()
15404 {       // remove all shelved variations
15405         int i;
15406         for(i=0; i<storedGames; i++) {
15407             if(savedDetails[i])
15408                 free(savedDetails[i]);
15409             savedDetails[i] = NULL;
15410         }
15411         for(i=framePtr; i<MAX_MOVES; i++) {
15412                 if(commentList[i]) free(commentList[i]);
15413                 commentList[i] = NULL;
15414         }
15415         framePtr = MAX_MOVES-1;
15416         storedGames = 0;
15417 }
15418
15419 void
15420 LoadVariation(int index, char *text)
15421 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15422         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15423         int level = 0, move;
15424
15425         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15426         // first find outermost bracketing variation
15427         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15428             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15429                 if(*p == '{') wait = '}'; else
15430                 if(*p == '[') wait = ']'; else
15431                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15432                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15433             }
15434             if(*p == wait) wait = NULLCHAR; // closing ]} found
15435             p++;
15436         }
15437         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15438         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15439         end[1] = NULLCHAR; // clip off comment beyond variation
15440         ToNrEvent(currentMove-1);
15441         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15442         // kludge: use ParsePV() to append variation to game
15443         move = currentMove;
15444         ParsePV(start, TRUE);
15445         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15446         ClearPremoveHighlights();
15447         CommentPopDown();
15448         ToNrEvent(currentMove+1);
15449 }
15450