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