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