fixed wrong default for polyglotDir mentioned in docs.
[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, 2011 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 && dst[count-1] != NULLCHAR)
322     {
323       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324       if(appData.debugMode)
325       fprintf(debugFP, "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 movesPerSession;
452 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
453 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
454 long timeControl_2; /* [AS] Allow separate time controls */
455 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
456 long timeRemaining[2][MAX_MOVES];
457 int matchGame = 0;
458 TimeMark programStartTime;
459 char ics_handle[MSG_SIZ];
460 int have_set_title = 0;
461
462 /* animateTraining preserves the state of appData.animate
463  * when Training mode is activated. This allows the
464  * response to be animated when appData.animate == TRUE and
465  * appData.animateDragging == TRUE.
466  */
467 Boolean animateTraining;
468
469 GameInfo gameInfo;
470
471 AppData appData;
472
473 Board boards[MAX_MOVES];
474 /* [HGM] Following 7 needed for accurate legality tests: */
475 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
476 signed char  initialRights[BOARD_FILES];
477 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
478 int   initialRulePlies, FENrulePlies;
479 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
480 int loadFlag = 0;
481 int shuffleOpenings;
482 int mute; // mute all sounds
483
484 // [HGM] vari: next 12 to save and restore variations
485 #define MAX_VARIATIONS 10
486 int framePtr = MAX_MOVES-1; // points to free stack entry
487 int storedGames = 0;
488 int savedFirst[MAX_VARIATIONS];
489 int savedLast[MAX_VARIATIONS];
490 int savedFramePtr[MAX_VARIATIONS];
491 char *savedDetails[MAX_VARIATIONS];
492 ChessMove savedResult[MAX_VARIATIONS];
493
494 void PushTail P((int firstMove, int lastMove));
495 Boolean PopTail P((Boolean annotate));
496 void CleanupTail P((void));
497
498 ChessSquare  FIDEArray[2][BOARD_FILES] = {
499     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
500         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
502         BlackKing, BlackBishop, BlackKnight, BlackRook }
503 };
504
505 ChessSquare twoKingsArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackKing, BlackKnight, BlackRook }
510 };
511
512 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
514         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
515     { BlackRook, BlackMan, BlackBishop, BlackQueen,
516         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
517 };
518
519 ChessSquare SpartanArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
523         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
524 };
525
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
530         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
531 };
532
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackMan, BlackFerz,
544         BlackKing, BlackMan, BlackKnight, BlackRook }
545 };
546
547
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
554 };
555
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
561 };
562
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
568 };
569
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
575 };
576
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
582 };
583
584 #ifdef GOTHIC
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
590 };
591 #else // !GOTHIC
592 #define GothicArray CapablancaArray
593 #endif // !GOTHIC
594
595 #ifdef FALCON
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
601 };
602 #else // !FALCON
603 #define FalconArray CapablancaArray
604 #endif // !FALCON
605
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
612
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
619 };
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
623
624
625 Board initialPosition;
626
627
628 /* Convert str to a rating. Checks for special cases of "----",
629
630    "++++", etc. Also strips ()'s */
631 int
632 string_to_rating(str)
633   char *str;
634 {
635   while(*str && !isdigit(*str)) ++str;
636   if (!*str)
637     return 0;   /* One of the special "no rating" cases */
638   else
639     return atoi(str);
640 }
641
642 void
643 ClearProgramStats()
644 {
645     /* Init programStats */
646     programStats.movelist[0] = 0;
647     programStats.depth = 0;
648     programStats.nr_moves = 0;
649     programStats.moves_left = 0;
650     programStats.nodes = 0;
651     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
652     programStats.score = 0;
653     programStats.got_only_move = 0;
654     programStats.got_fail = 0;
655     programStats.line_is_book = 0;
656 }
657
658 void
659 InitBackEnd1()
660 {
661     int matched, min, sec;
662
663     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
665
666     GetTimeMark(&programStartTime);
667     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
668
669     ClearProgramStats();
670     programStats.ok_to_send = 1;
671     programStats.seen_stat = 0;
672
673     /*
674      * Initialize game list
675      */
676     ListNew(&gameList);
677
678
679     /*
680      * Internet chess server status
681      */
682     if (appData.icsActive) {
683         appData.matchMode = FALSE;
684         appData.matchGames = 0;
685 #if ZIPPY
686         appData.noChessProgram = !appData.zippyPlay;
687 #else
688         appData.zippyPlay = FALSE;
689         appData.zippyTalk = FALSE;
690         appData.noChessProgram = TRUE;
691 #endif
692         if (*appData.icsHelper != NULLCHAR) {
693             appData.useTelnet = TRUE;
694             appData.telnetProgram = appData.icsHelper;
695         }
696     } else {
697         appData.zippyTalk = appData.zippyPlay = FALSE;
698     }
699
700     /* [AS] Initialize pv info list [HGM] and game state */
701     {
702         int i, j;
703
704         for( i=0; i<=framePtr; i++ ) {
705             pvInfoList[i].depth = -1;
706             boards[i][EP_STATUS] = EP_NONE;
707             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708         }
709     }
710
711     /*
712      * Parse timeControl resource
713      */
714     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715                           appData.movesPerSession)) {
716         char buf[MSG_SIZ];
717         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718         DisplayFatalError(buf, 0, 2);
719     }
720
721     /*
722      * Parse searchTime resource
723      */
724     if (*appData.searchTime != NULLCHAR) {
725         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
726         if (matched == 1) {
727             searchTime = min * 60;
728         } else if (matched == 2) {
729             searchTime = min * 60 + sec;
730         } else {
731             char buf[MSG_SIZ];
732             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733             DisplayFatalError(buf, 0, 2);
734         }
735     }
736
737     /* [AS] Adjudication threshold */
738     adjudicateLossThreshold = appData.adjudicateLossThreshold;
739
740     first.which = "first";
741     second.which = "second";
742     first.maybeThinking = second.maybeThinking = FALSE;
743     first.pr = second.pr = NoProc;
744     first.isr = second.isr = NULL;
745     first.sendTime = second.sendTime = 2;
746     first.sendDrawOffers = 1;
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754     first.program = appData.firstChessProgram;
755     second.program = appData.secondChessProgram;
756     first.host = appData.firstHost;
757     second.host = appData.secondHost;
758     first.dir = appData.firstDirectory;
759     second.dir = appData.secondDirectory;
760     first.other = &second;
761     second.other = &first;
762     first.initString = appData.initString;
763     second.initString = appData.secondInitString;
764     first.computerString = appData.firstComputerString;
765     second.computerString = appData.secondComputerString;
766     first.useSigint = second.useSigint = TRUE;
767     first.useSigterm = second.useSigterm = TRUE;
768     first.reuse = appData.reuseFirst;
769     second.reuse = appData.reuseSecond;
770     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
771     second.nps = appData.secondNPS;
772     first.useSetboard = second.useSetboard = FALSE;
773     first.useSAN = second.useSAN = FALSE;
774     first.usePing = second.usePing = FALSE;
775     first.lastPing = second.lastPing = 0;
776     first.lastPong = second.lastPong = 0;
777     first.usePlayother = second.usePlayother = FALSE;
778     first.useColors = second.useColors = TRUE;
779     first.useUsermove = second.useUsermove = FALSE;
780     first.sendICS = second.sendICS = FALSE;
781     first.sendName = second.sendName = appData.icsActive;
782     first.sdKludge = second.sdKludge = FALSE;
783     first.stKludge = second.stKludge = FALSE;
784     TidyProgramName(first.program, first.host, first.tidy);
785     TidyProgramName(second.program, second.host, second.tidy);
786     first.matchWins = second.matchWins = 0;
787     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789     first.analysisSupport = second.analysisSupport = 2; /* detect */
790     first.analyzing = second.analyzing = FALSE;
791     first.initDone = second.initDone = FALSE;
792
793     /* New features added by Tord: */
794     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     first.fenOverride  = appData.fenOverride1;
798     second.fenOverride = appData.fenOverride2;
799
800     /* [HGM] time odds: set factor for each machine */
801     first.timeOdds  = appData.firstTimeOdds;
802     second.timeOdds = appData.secondTimeOdds;
803     { float norm = 1;
804         if(appData.timeOddsMode) {
805             norm = first.timeOdds;
806             if(norm > second.timeOdds) norm = second.timeOdds;
807         }
808         first.timeOdds /= norm;
809         second.timeOdds /= norm;
810     }
811
812     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813     first.accumulateTC = appData.firstAccumulateTC;
814     second.accumulateTC = appData.secondAccumulateTC;
815     first.maxNrOfSessions = second.maxNrOfSessions = 1;
816
817     /* [HGM] debug */
818     first.debug = second.debug = FALSE;
819     first.supportsNPS = second.supportsNPS = UNKNOWN;
820
821     /* [HGM] options */
822     first.optionSettings  = appData.firstOptions;
823     second.optionSettings = appData.secondOptions;
824
825     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827     first.isUCI = appData.firstIsUCI; /* [AS] */
828     second.isUCI = appData.secondIsUCI; /* [AS] */
829     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
831
832     if (appData.firstProtocolVersion > PROTOVER
833         || appData.firstProtocolVersion < 1)
834       {
835         char buf[MSG_SIZ];
836         int len;
837
838         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839                        appData.firstProtocolVersion);
840         if( (len > MSG_SIZ) && appData.debugMode )
841           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842
843         DisplayFatalError(buf, 0, 2);
844       }
845     else
846       {
847         first.protocolVersion = appData.firstProtocolVersion;
848       }
849
850     if (appData.secondProtocolVersion > PROTOVER
851         || appData.secondProtocolVersion < 1)
852       {
853         char buf[MSG_SIZ];
854         int len;
855
856         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857                        appData.secondProtocolVersion);
858         if( (len > MSG_SIZ) && appData.debugMode )
859           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
860
861         DisplayFatalError(buf, 0, 2);
862       }
863     else
864       {
865         second.protocolVersion = appData.secondProtocolVersion;
866       }
867
868     if (appData.icsActive) {
869         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
870 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872         appData.clockMode = FALSE;
873         first.sendTime = second.sendTime = 0;
874     }
875
876 #if ZIPPY
877     /* Override some settings from environment variables, for backward
878        compatibility.  Unfortunately it's not feasible to have the env
879        vars just set defaults, at least in xboard.  Ugh.
880     */
881     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882       ZippyInit();
883     }
884 #endif
885
886     if (appData.noChessProgram) {
887         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888         sprintf(programVersion, "%s", PACKAGE_STRING);
889     } else {
890       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
893     }
894
895     if (!appData.icsActive) {
896       char buf[MSG_SIZ];
897       int len;
898
899       /* Check for variants that are supported only in ICS mode,
900          or not at all.  Some that are accepted here nevertheless
901          have bugs; see comments below.
902       */
903       VariantClass variant = StringToVariant(appData.variant);
904       switch (variant) {
905       case VariantBughouse:     /* need four players and two boards */
906       case VariantKriegspiel:   /* need to hide pieces and move details */
907         /* case VariantFischeRandom: (Fabien: moved below) */
908         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909         if( (len > MSG_SIZ) && appData.debugMode )
910           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
911
912         DisplayFatalError(buf, 0, 2);
913         return;
914
915       case VariantUnknown:
916       case VariantLoadable:
917       case Variant29:
918       case Variant30:
919       case Variant31:
920       case Variant32:
921       case Variant33:
922       case Variant34:
923       case Variant35:
924       case Variant36:
925       default:
926         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927         if( (len > MSG_SIZ) && appData.debugMode )
928           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
929
930         DisplayFatalError(buf, 0, 2);
931         return;
932
933       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
934       case VariantFairy:      /* [HGM] TestLegality definitely off! */
935       case VariantGothic:     /* [HGM] should work */
936       case VariantCapablanca: /* [HGM] should work */
937       case VariantCourier:    /* [HGM] initial forced moves not implemented */
938       case VariantShogi:      /* [HGM] could still mate with pawn drop */
939       case VariantKnightmate: /* [HGM] should work */
940       case VariantCylinder:   /* [HGM] untested */
941       case VariantFalcon:     /* [HGM] untested */
942       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943                                  offboard interposition not understood */
944       case VariantNormal:     /* definitely works! */
945       case VariantWildCastle: /* pieces not automatically shuffled */
946       case VariantNoCastle:   /* pieces not automatically shuffled */
947       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948       case VariantLosers:     /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantSuicide:    /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantGiveaway:   /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantTwoKings:   /* should work */
955       case VariantAtomic:     /* should work except for win condition */
956       case Variant3Check:     /* should work except for win condition */
957       case VariantShatranj:   /* should work except for all win conditions */
958       case VariantMakruk:     /* should work except for daw countdown */
959       case VariantBerolina:   /* might work if TestLegality is off */
960       case VariantCapaRandom: /* should work */
961       case VariantJanus:      /* should work */
962       case VariantSuper:      /* experimental */
963       case VariantGreat:      /* experimental, requires legality testing to be off */
964       case VariantSChess:     /* S-Chess, should work */
965       case VariantSpartan:    /* should work */
966         break;
967       }
968     }
969
970     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
971     InitEngineUCI( installDir, &second );
972 }
973
974 int NextIntegerFromString( char ** str, long * value )
975 {
976     int result = -1;
977     char * s = *str;
978
979     while( *s == ' ' || *s == '\t' ) {
980         s++;
981     }
982
983     *value = 0;
984
985     if( *s >= '0' && *s <= '9' ) {
986         while( *s >= '0' && *s <= '9' ) {
987             *value = *value * 10 + (*s - '0');
988             s++;
989         }
990
991         result = 0;
992     }
993
994     *str = s;
995
996     return result;
997 }
998
999 int NextTimeControlFromString( char ** str, long * value )
1000 {
1001     long temp;
1002     int result = NextIntegerFromString( str, &temp );
1003
1004     if( result == 0 ) {
1005         *value = temp * 60; /* Minutes */
1006         if( **str == ':' ) {
1007             (*str)++;
1008             result = NextIntegerFromString( str, &temp );
1009             *value += temp; /* Seconds */
1010         }
1011     }
1012
1013     return result;
1014 }
1015
1016 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1017 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1018     int result = -1, type = 0; long temp, temp2;
1019
1020     if(**str != ':') return -1; // old params remain in force!
1021     (*str)++;
1022     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1023     if( NextIntegerFromString( str, &temp ) ) return -1;
1024     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1025
1026     if(**str != '/') {
1027         /* time only: incremental or sudden-death time control */
1028         if(**str == '+') { /* increment follows; read it */
1029             (*str)++;
1030             if(**str == '!') type = *(*str)++; // Bronstein TC
1031             if(result = NextIntegerFromString( str, &temp2)) return -1;
1032             *inc = temp2 * 1000;
1033             if(**str == '.') { // read fraction of increment
1034                 char *start = ++(*str);
1035                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1036                 temp2 *= 1000;
1037                 while(start++ < *str) temp2 /= 10;
1038                 *inc += temp2;
1039             }
1040         } else *inc = 0;
1041         *moves = 0; *tc = temp * 1000; *incType = type;
1042         return 0;
1043     }
1044
1045     (*str)++; /* classical time control */
1046     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1047
1048     if(result == 0) {
1049         *moves = temp;
1050         *tc    = temp2 * 1000;
1051         *inc   = 0;
1052         *incType = type;
1053     }
1054     return result;
1055 }
1056
1057 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1058 {   /* [HGM] get time to add from the multi-session time-control string */
1059     int incType, moves=1; /* kludge to force reading of first session */
1060     long time, increment;
1061     char *s = tcString;
1062
1063     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1064     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1065     do {
1066         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1067         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1068         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1069         if(movenr == -1) return time;    /* last move before new session     */
1070         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1071         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1072         if(!moves) return increment;     /* current session is incremental   */
1073         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1074     } while(movenr >= -1);               /* try again for next session       */
1075
1076     return 0; // no new time quota on this move
1077 }
1078
1079 int
1080 ParseTimeControl(tc, ti, mps)
1081      char *tc;
1082      float ti;
1083      int mps;
1084 {
1085   long tc1;
1086   long tc2;
1087   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1088   int min, sec=0;
1089
1090   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1091   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1092       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1093   if(ti > 0) {
1094
1095     if(mps)
1096       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1097     else 
1098       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1099   } else {
1100     if(mps)
1101       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1102     else 
1103       snprintf(buf, MSG_SIZ, ":%s", mytc);
1104   }
1105   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1106   
1107   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1108     return FALSE;
1109   }
1110
1111   if( *tc == '/' ) {
1112     /* Parse second time control */
1113     tc++;
1114
1115     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1116       return FALSE;
1117     }
1118
1119     if( tc2 == 0 ) {
1120       return FALSE;
1121     }
1122
1123     timeControl_2 = tc2 * 1000;
1124   }
1125   else {
1126     timeControl_2 = 0;
1127   }
1128
1129   if( tc1 == 0 ) {
1130     return FALSE;
1131   }
1132
1133   timeControl = tc1 * 1000;
1134
1135   if (ti >= 0) {
1136     timeIncrement = ti * 1000;  /* convert to ms */
1137     movesPerSession = 0;
1138   } else {
1139     timeIncrement = 0;
1140     movesPerSession = mps;
1141   }
1142   return TRUE;
1143 }
1144
1145 void
1146 InitBackEnd2()
1147 {
1148     if (appData.debugMode) {
1149         fprintf(debugFP, "%s\n", programVersion);
1150     }
1151
1152     set_cont_sequence(appData.wrapContSeq);
1153     if (appData.matchGames > 0) {
1154         appData.matchMode = TRUE;
1155     } else if (appData.matchMode) {
1156         appData.matchGames = 1;
1157     }
1158     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1159         appData.matchGames = appData.sameColorGames;
1160     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1161         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1162         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1163     }
1164     Reset(TRUE, FALSE);
1165     if (appData.noChessProgram || first.protocolVersion == 1) {
1166       InitBackEnd3();
1167     } else {
1168       /* kludge: allow timeout for initial "feature" commands */
1169       FreezeUI();
1170       DisplayMessage("", _("Starting chess program"));
1171       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1172     }
1173 }
1174
1175 void
1176 InitBackEnd3 P((void))
1177 {
1178     GameMode initialMode;
1179     char buf[MSG_SIZ];
1180     int err, len;
1181
1182     InitChessProgram(&first, startedFromSetupPosition);
1183
1184     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1185         free(programVersion);
1186         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1187         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1188     }
1189
1190     if (appData.icsActive) {
1191 #ifdef WIN32
1192         /* [DM] Make a console window if needed [HGM] merged ifs */
1193         ConsoleCreate();
1194 #endif
1195         err = establish();
1196         if (err != 0)
1197           {
1198             if (*appData.icsCommPort != NULLCHAR)
1199               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1200                              appData.icsCommPort);
1201             else
1202               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1203                         appData.icsHost, appData.icsPort);
1204
1205             if( (len > MSG_SIZ) && appData.debugMode )
1206               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1207
1208             DisplayFatalError(buf, err, 1);
1209             return;
1210         }
1211         SetICSMode();
1212         telnetISR =
1213           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1214         fromUserISR =
1215           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1216         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1217             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1218     } else if (appData.noChessProgram) {
1219         SetNCPMode();
1220     } else {
1221         SetGNUMode();
1222     }
1223
1224     if (*appData.cmailGameName != NULLCHAR) {
1225         SetCmailMode();
1226         OpenLoopback(&cmailPR);
1227         cmailISR =
1228           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1229     }
1230
1231     ThawUI();
1232     DisplayMessage("", "");
1233     if (StrCaseCmp(appData.initialMode, "") == 0) {
1234       initialMode = BeginningOfGame;
1235     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1236       initialMode = TwoMachinesPlay;
1237     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1238       initialMode = AnalyzeFile;
1239     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1240       initialMode = AnalyzeMode;
1241     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1242       initialMode = MachinePlaysWhite;
1243     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1244       initialMode = MachinePlaysBlack;
1245     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1246       initialMode = EditGame;
1247     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1248       initialMode = EditPosition;
1249     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1250       initialMode = Training;
1251     } else {
1252       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1253       if( (len > MSG_SIZ) && appData.debugMode )
1254         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1255
1256       DisplayFatalError(buf, 0, 2);
1257       return;
1258     }
1259
1260     if (appData.matchMode) {
1261         /* Set up machine vs. machine match */
1262         if (appData.noChessProgram) {
1263             DisplayFatalError(_("Can't have a match with no chess programs"),
1264                               0, 2);
1265             return;
1266         }
1267         matchMode = TRUE;
1268         matchGame = 1;
1269         if (*appData.loadGameFile != NULLCHAR) {
1270             int index = appData.loadGameIndex; // [HGM] autoinc
1271             if(index<0) lastIndex = index = 1;
1272             if (!LoadGameFromFile(appData.loadGameFile,
1273                                   index,
1274                                   appData.loadGameFile, FALSE)) {
1275                 DisplayFatalError(_("Bad game file"), 0, 1);
1276                 return;
1277             }
1278         } else if (*appData.loadPositionFile != NULLCHAR) {
1279             int index = appData.loadPositionIndex; // [HGM] autoinc
1280             if(index<0) lastIndex = index = 1;
1281             if (!LoadPositionFromFile(appData.loadPositionFile,
1282                                       index,
1283                                       appData.loadPositionFile)) {
1284                 DisplayFatalError(_("Bad position file"), 0, 1);
1285                 return;
1286             }
1287         }
1288         TwoMachinesEvent();
1289     } else if (*appData.cmailGameName != NULLCHAR) {
1290         /* Set up cmail mode */
1291         ReloadCmailMsgEvent(TRUE);
1292     } else {
1293         /* Set up other modes */
1294         if (initialMode == AnalyzeFile) {
1295           if (*appData.loadGameFile == NULLCHAR) {
1296             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1297             return;
1298           }
1299         }
1300         if (*appData.loadGameFile != NULLCHAR) {
1301             (void) LoadGameFromFile(appData.loadGameFile,
1302                                     appData.loadGameIndex,
1303                                     appData.loadGameFile, TRUE);
1304         } else if (*appData.loadPositionFile != NULLCHAR) {
1305             (void) LoadPositionFromFile(appData.loadPositionFile,
1306                                         appData.loadPositionIndex,
1307                                         appData.loadPositionFile);
1308             /* [HGM] try to make self-starting even after FEN load */
1309             /* to allow automatic setup of fairy variants with wtm */
1310             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1311                 gameMode = BeginningOfGame;
1312                 setboardSpoiledMachineBlack = 1;
1313             }
1314             /* [HGM] loadPos: make that every new game uses the setup */
1315             /* from file as long as we do not switch variant          */
1316             if(!blackPlaysFirst) {
1317                 startedFromPositionFile = TRUE;
1318                 CopyBoard(filePosition, boards[0]);
1319             }
1320         }
1321         if (initialMode == AnalyzeMode) {
1322           if (appData.noChessProgram) {
1323             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1324             return;
1325           }
1326           if (appData.icsActive) {
1327             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1328             return;
1329           }
1330           AnalyzeModeEvent();
1331         } else if (initialMode == AnalyzeFile) {
1332           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1333           ShowThinkingEvent();
1334           AnalyzeFileEvent();
1335           AnalysisPeriodicEvent(1);
1336         } else if (initialMode == MachinePlaysWhite) {
1337           if (appData.noChessProgram) {
1338             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1339                               0, 2);
1340             return;
1341           }
1342           if (appData.icsActive) {
1343             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1344                               0, 2);
1345             return;
1346           }
1347           MachineWhiteEvent();
1348         } else if (initialMode == MachinePlaysBlack) {
1349           if (appData.noChessProgram) {
1350             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1351                               0, 2);
1352             return;
1353           }
1354           if (appData.icsActive) {
1355             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1356                               0, 2);
1357             return;
1358           }
1359           MachineBlackEvent();
1360         } else if (initialMode == TwoMachinesPlay) {
1361           if (appData.noChessProgram) {
1362             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1363                               0, 2);
1364             return;
1365           }
1366           if (appData.icsActive) {
1367             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1368                               0, 2);
1369             return;
1370           }
1371           TwoMachinesEvent();
1372         } else if (initialMode == EditGame) {
1373           EditGameEvent();
1374         } else if (initialMode == EditPosition) {
1375           EditPositionEvent();
1376         } else if (initialMode == Training) {
1377           if (*appData.loadGameFile == NULLCHAR) {
1378             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1379             return;
1380           }
1381           TrainingEvent();
1382         }
1383     }
1384 }
1385
1386 /*
1387  * Establish will establish a contact to a remote host.port.
1388  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1389  *  used to talk to the host.
1390  * Returns 0 if okay, error code if not.
1391  */
1392 int
1393 establish()
1394 {
1395     char buf[MSG_SIZ];
1396
1397     if (*appData.icsCommPort != NULLCHAR) {
1398         /* Talk to the host through a serial comm port */
1399         return OpenCommPort(appData.icsCommPort, &icsPR);
1400
1401     } else if (*appData.gateway != NULLCHAR) {
1402         if (*appData.remoteShell == NULLCHAR) {
1403             /* Use the rcmd protocol to run telnet program on a gateway host */
1404             snprintf(buf, sizeof(buf), "%s %s %s",
1405                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1406             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1407
1408         } else {
1409             /* Use the rsh program to run telnet program on a gateway host */
1410             if (*appData.remoteUser == NULLCHAR) {
1411                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1412                         appData.gateway, appData.telnetProgram,
1413                         appData.icsHost, appData.icsPort);
1414             } else {
1415                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1416                         appData.remoteShell, appData.gateway,
1417                         appData.remoteUser, appData.telnetProgram,
1418                         appData.icsHost, appData.icsPort);
1419             }
1420             return StartChildProcess(buf, "", &icsPR);
1421
1422         }
1423     } else if (appData.useTelnet) {
1424         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1425
1426     } else {
1427         /* TCP socket interface differs somewhat between
1428            Unix and NT; handle details in the front end.
1429            */
1430         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1431     }
1432 }
1433
1434 void EscapeExpand(char *p, char *q)
1435 {       // [HGM] initstring: routine to shape up string arguments
1436         while(*p++ = *q++) if(p[-1] == '\\')
1437             switch(*q++) {
1438                 case 'n': p[-1] = '\n'; break;
1439                 case 'r': p[-1] = '\r'; break;
1440                 case 't': p[-1] = '\t'; break;
1441                 case '\\': p[-1] = '\\'; break;
1442                 case 0: *p = 0; return;
1443                 default: p[-1] = q[-1]; break;
1444             }
1445 }
1446
1447 void
1448 show_bytes(fp, buf, count)
1449      FILE *fp;
1450      char *buf;
1451      int count;
1452 {
1453     while (count--) {
1454         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1455             fprintf(fp, "\\%03o", *buf & 0xff);
1456         } else {
1457             putc(*buf, fp);
1458         }
1459         buf++;
1460     }
1461     fflush(fp);
1462 }
1463
1464 /* Returns an errno value */
1465 int
1466 OutputMaybeTelnet(pr, message, count, outError)
1467      ProcRef pr;
1468      char *message;
1469      int count;
1470      int *outError;
1471 {
1472     char buf[8192], *p, *q, *buflim;
1473     int left, newcount, outcount;
1474
1475     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1476         *appData.gateway != NULLCHAR) {
1477         if (appData.debugMode) {
1478             fprintf(debugFP, ">ICS: ");
1479             show_bytes(debugFP, message, count);
1480             fprintf(debugFP, "\n");
1481         }
1482         return OutputToProcess(pr, message, count, outError);
1483     }
1484
1485     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1486     p = message;
1487     q = buf;
1488     left = count;
1489     newcount = 0;
1490     while (left) {
1491         if (q >= buflim) {
1492             if (appData.debugMode) {
1493                 fprintf(debugFP, ">ICS: ");
1494                 show_bytes(debugFP, buf, newcount);
1495                 fprintf(debugFP, "\n");
1496             }
1497             outcount = OutputToProcess(pr, buf, newcount, outError);
1498             if (outcount < newcount) return -1; /* to be sure */
1499             q = buf;
1500             newcount = 0;
1501         }
1502         if (*p == '\n') {
1503             *q++ = '\r';
1504             newcount++;
1505         } else if (((unsigned char) *p) == TN_IAC) {
1506             *q++ = (char) TN_IAC;
1507             newcount ++;
1508         }
1509         *q++ = *p++;
1510         newcount++;
1511         left--;
1512     }
1513     if (appData.debugMode) {
1514         fprintf(debugFP, ">ICS: ");
1515         show_bytes(debugFP, buf, newcount);
1516         fprintf(debugFP, "\n");
1517     }
1518     outcount = OutputToProcess(pr, buf, newcount, outError);
1519     if (outcount < newcount) return -1; /* to be sure */
1520     return count;
1521 }
1522
1523 void
1524 read_from_player(isr, closure, message, count, error)
1525      InputSourceRef isr;
1526      VOIDSTAR closure;
1527      char *message;
1528      int count;
1529      int error;
1530 {
1531     int outError, outCount;
1532     static int gotEof = 0;
1533
1534     /* Pass data read from player on to ICS */
1535     if (count > 0) {
1536         gotEof = 0;
1537         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1538         if (outCount < count) {
1539             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1540         }
1541     } else if (count < 0) {
1542         RemoveInputSource(isr);
1543         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1544     } else if (gotEof++ > 0) {
1545         RemoveInputSource(isr);
1546         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1547     }
1548 }
1549
1550 void
1551 KeepAlive()
1552 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1553     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1554     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1555     SendToICS("date\n");
1556     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1557 }
1558
1559 /* added routine for printf style output to ics */
1560 void ics_printf(char *format, ...)
1561 {
1562     char buffer[MSG_SIZ];
1563     va_list args;
1564
1565     va_start(args, format);
1566     vsnprintf(buffer, sizeof(buffer), format, args);
1567     buffer[sizeof(buffer)-1] = '\0';
1568     SendToICS(buffer);
1569     va_end(args);
1570 }
1571
1572 void
1573 SendToICS(s)
1574      char *s;
1575 {
1576     int count, outCount, outError;
1577
1578     if (icsPR == NULL) return;
1579
1580     count = strlen(s);
1581     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1582     if (outCount < count) {
1583         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1584     }
1585 }
1586
1587 /* This is used for sending logon scripts to the ICS. Sending
1588    without a delay causes problems when using timestamp on ICC
1589    (at least on my machine). */
1590 void
1591 SendToICSDelayed(s,msdelay)
1592      char *s;
1593      long msdelay;
1594 {
1595     int count, outCount, outError;
1596
1597     if (icsPR == NULL) return;
1598
1599     count = strlen(s);
1600     if (appData.debugMode) {
1601         fprintf(debugFP, ">ICS: ");
1602         show_bytes(debugFP, s, count);
1603         fprintf(debugFP, "\n");
1604     }
1605     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1606                                       msdelay);
1607     if (outCount < count) {
1608         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1609     }
1610 }
1611
1612
1613 /* Remove all highlighting escape sequences in s
1614    Also deletes any suffix starting with '('
1615    */
1616 char *
1617 StripHighlightAndTitle(s)
1618      char *s;
1619 {
1620     static char retbuf[MSG_SIZ];
1621     char *p = retbuf;
1622
1623     while (*s != NULLCHAR) {
1624         while (*s == '\033') {
1625             while (*s != NULLCHAR && !isalpha(*s)) s++;
1626             if (*s != NULLCHAR) s++;
1627         }
1628         while (*s != NULLCHAR && *s != '\033') {
1629             if (*s == '(' || *s == '[') {
1630                 *p = NULLCHAR;
1631                 return retbuf;
1632             }
1633             *p++ = *s++;
1634         }
1635     }
1636     *p = NULLCHAR;
1637     return retbuf;
1638 }
1639
1640 /* Remove all highlighting escape sequences in s */
1641 char *
1642 StripHighlight(s)
1643      char *s;
1644 {
1645     static char retbuf[MSG_SIZ];
1646     char *p = retbuf;
1647
1648     while (*s != NULLCHAR) {
1649         while (*s == '\033') {
1650             while (*s != NULLCHAR && !isalpha(*s)) s++;
1651             if (*s != NULLCHAR) s++;
1652         }
1653         while (*s != NULLCHAR && *s != '\033') {
1654             *p++ = *s++;
1655         }
1656     }
1657     *p = NULLCHAR;
1658     return retbuf;
1659 }
1660
1661 char *variantNames[] = VARIANT_NAMES;
1662 char *
1663 VariantName(v)
1664      VariantClass v;
1665 {
1666     return variantNames[v];
1667 }
1668
1669
1670 /* Identify a variant from the strings the chess servers use or the
1671    PGN Variant tag names we use. */
1672 VariantClass
1673 StringToVariant(e)
1674      char *e;
1675 {
1676     char *p;
1677     int wnum = -1;
1678     VariantClass v = VariantNormal;
1679     int i, found = FALSE;
1680     char buf[MSG_SIZ];
1681     int len;
1682
1683     if (!e) return v;
1684
1685     /* [HGM] skip over optional board-size prefixes */
1686     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1687         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1688         while( *e++ != '_');
1689     }
1690
1691     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1692         v = VariantNormal;
1693         found = TRUE;
1694     } else
1695     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1696       if (StrCaseStr(e, variantNames[i])) {
1697         v = (VariantClass) i;
1698         found = TRUE;
1699         break;
1700       }
1701     }
1702
1703     if (!found) {
1704       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1705           || StrCaseStr(e, "wild/fr")
1706           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1707         v = VariantFischeRandom;
1708       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1709                  (i = 1, p = StrCaseStr(e, "w"))) {
1710         p += i;
1711         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1712         if (isdigit(*p)) {
1713           wnum = atoi(p);
1714         } else {
1715           wnum = -1;
1716         }
1717         switch (wnum) {
1718         case 0: /* FICS only, actually */
1719         case 1:
1720           /* Castling legal even if K starts on d-file */
1721           v = VariantWildCastle;
1722           break;
1723         case 2:
1724         case 3:
1725         case 4:
1726           /* Castling illegal even if K & R happen to start in
1727              normal positions. */
1728           v = VariantNoCastle;
1729           break;
1730         case 5:
1731         case 7:
1732         case 8:
1733         case 10:
1734         case 11:
1735         case 12:
1736         case 13:
1737         case 14:
1738         case 15:
1739         case 18:
1740         case 19:
1741           /* Castling legal iff K & R start in normal positions */
1742           v = VariantNormal;
1743           break;
1744         case 6:
1745         case 20:
1746         case 21:
1747           /* Special wilds for position setup; unclear what to do here */
1748           v = VariantLoadable;
1749           break;
1750         case 9:
1751           /* Bizarre ICC game */
1752           v = VariantTwoKings;
1753           break;
1754         case 16:
1755           v = VariantKriegspiel;
1756           break;
1757         case 17:
1758           v = VariantLosers;
1759           break;
1760         case 22:
1761           v = VariantFischeRandom;
1762           break;
1763         case 23:
1764           v = VariantCrazyhouse;
1765           break;
1766         case 24:
1767           v = VariantBughouse;
1768           break;
1769         case 25:
1770           v = Variant3Check;
1771           break;
1772         case 26:
1773           /* Not quite the same as FICS suicide! */
1774           v = VariantGiveaway;
1775           break;
1776         case 27:
1777           v = VariantAtomic;
1778           break;
1779         case 28:
1780           v = VariantShatranj;
1781           break;
1782
1783         /* Temporary names for future ICC types.  The name *will* change in
1784            the next xboard/WinBoard release after ICC defines it. */
1785         case 29:
1786           v = Variant29;
1787           break;
1788         case 30:
1789           v = Variant30;
1790           break;
1791         case 31:
1792           v = Variant31;
1793           break;
1794         case 32:
1795           v = Variant32;
1796           break;
1797         case 33:
1798           v = Variant33;
1799           break;
1800         case 34:
1801           v = Variant34;
1802           break;
1803         case 35:
1804           v = Variant35;
1805           break;
1806         case 36:
1807           v = Variant36;
1808           break;
1809         case 37:
1810           v = VariantShogi;
1811           break;
1812         case 38:
1813           v = VariantXiangqi;
1814           break;
1815         case 39:
1816           v = VariantCourier;
1817           break;
1818         case 40:
1819           v = VariantGothic;
1820           break;
1821         case 41:
1822           v = VariantCapablanca;
1823           break;
1824         case 42:
1825           v = VariantKnightmate;
1826           break;
1827         case 43:
1828           v = VariantFairy;
1829           break;
1830         case 44:
1831           v = VariantCylinder;
1832           break;
1833         case 45:
1834           v = VariantFalcon;
1835           break;
1836         case 46:
1837           v = VariantCapaRandom;
1838           break;
1839         case 47:
1840           v = VariantBerolina;
1841           break;
1842         case 48:
1843           v = VariantJanus;
1844           break;
1845         case 49:
1846           v = VariantSuper;
1847           break;
1848         case 50:
1849           v = VariantGreat;
1850           break;
1851         case -1:
1852           /* Found "wild" or "w" in the string but no number;
1853              must assume it's normal chess. */
1854           v = VariantNormal;
1855           break;
1856         default:
1857           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1858           if( (len > MSG_SIZ) && appData.debugMode )
1859             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1860
1861           DisplayError(buf, 0);
1862           v = VariantUnknown;
1863           break;
1864         }
1865       }
1866     }
1867     if (appData.debugMode) {
1868       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1869               e, wnum, VariantName(v));
1870     }
1871     return v;
1872 }
1873
1874 static int leftover_start = 0, leftover_len = 0;
1875 char star_match[STAR_MATCH_N][MSG_SIZ];
1876
1877 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1878    advance *index beyond it, and set leftover_start to the new value of
1879    *index; else return FALSE.  If pattern contains the character '*', it
1880    matches any sequence of characters not containing '\r', '\n', or the
1881    character following the '*' (if any), and the matched sequence(s) are
1882    copied into star_match.
1883    */
1884 int
1885 looking_at(buf, index, pattern)
1886      char *buf;
1887      int *index;
1888      char *pattern;
1889 {
1890     char *bufp = &buf[*index], *patternp = pattern;
1891     int star_count = 0;
1892     char *matchp = star_match[0];
1893
1894     for (;;) {
1895         if (*patternp == NULLCHAR) {
1896             *index = leftover_start = bufp - buf;
1897             *matchp = NULLCHAR;
1898             return TRUE;
1899         }
1900         if (*bufp == NULLCHAR) return FALSE;
1901         if (*patternp == '*') {
1902             if (*bufp == *(patternp + 1)) {
1903                 *matchp = NULLCHAR;
1904                 matchp = star_match[++star_count];
1905                 patternp += 2;
1906                 bufp++;
1907                 continue;
1908             } else if (*bufp == '\n' || *bufp == '\r') {
1909                 patternp++;
1910                 if (*patternp == NULLCHAR)
1911                   continue;
1912                 else
1913                   return FALSE;
1914             } else {
1915                 *matchp++ = *bufp++;
1916                 continue;
1917             }
1918         }
1919         if (*patternp != *bufp) return FALSE;
1920         patternp++;
1921         bufp++;
1922     }
1923 }
1924
1925 void
1926 SendToPlayer(data, length)
1927      char *data;
1928      int length;
1929 {
1930     int error, outCount;
1931     outCount = OutputToProcess(NoProc, data, length, &error);
1932     if (outCount < length) {
1933         DisplayFatalError(_("Error writing to display"), error, 1);
1934     }
1935 }
1936
1937 void
1938 PackHolding(packed, holding)
1939      char packed[];
1940      char *holding;
1941 {
1942     char *p = holding;
1943     char *q = packed;
1944     int runlength = 0;
1945     int curr = 9999;
1946     do {
1947         if (*p == curr) {
1948             runlength++;
1949         } else {
1950             switch (runlength) {
1951               case 0:
1952                 break;
1953               case 1:
1954                 *q++ = curr;
1955                 break;
1956               case 2:
1957                 *q++ = curr;
1958                 *q++ = curr;
1959                 break;
1960               default:
1961                 sprintf(q, "%d", runlength);
1962                 while (*q) q++;
1963                 *q++ = curr;
1964                 break;
1965             }
1966             runlength = 1;
1967             curr = *p;
1968         }
1969     } while (*p++);
1970     *q = NULLCHAR;
1971 }
1972
1973 /* Telnet protocol requests from the front end */
1974 void
1975 TelnetRequest(ddww, option)
1976      unsigned char ddww, option;
1977 {
1978     unsigned char msg[3];
1979     int outCount, outError;
1980
1981     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1982
1983     if (appData.debugMode) {
1984         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1985         switch (ddww) {
1986           case TN_DO:
1987             ddwwStr = "DO";
1988             break;
1989           case TN_DONT:
1990             ddwwStr = "DONT";
1991             break;
1992           case TN_WILL:
1993             ddwwStr = "WILL";
1994             break;
1995           case TN_WONT:
1996             ddwwStr = "WONT";
1997             break;
1998           default:
1999             ddwwStr = buf1;
2000             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2001             break;
2002         }
2003         switch (option) {
2004           case TN_ECHO:
2005             optionStr = "ECHO";
2006             break;
2007           default:
2008             optionStr = buf2;
2009             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2010             break;
2011         }
2012         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2013     }
2014     msg[0] = TN_IAC;
2015     msg[1] = ddww;
2016     msg[2] = option;
2017     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2018     if (outCount < 3) {
2019         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2020     }
2021 }
2022
2023 void
2024 DoEcho()
2025 {
2026     if (!appData.icsActive) return;
2027     TelnetRequest(TN_DO, TN_ECHO);
2028 }
2029
2030 void
2031 DontEcho()
2032 {
2033     if (!appData.icsActive) return;
2034     TelnetRequest(TN_DONT, TN_ECHO);
2035 }
2036
2037 void
2038 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2039 {
2040     /* put the holdings sent to us by the server on the board holdings area */
2041     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2042     char p;
2043     ChessSquare piece;
2044
2045     if(gameInfo.holdingsWidth < 2)  return;
2046     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2047         return; // prevent overwriting by pre-board holdings
2048
2049     if( (int)lowestPiece >= BlackPawn ) {
2050         holdingsColumn = 0;
2051         countsColumn = 1;
2052         holdingsStartRow = BOARD_HEIGHT-1;
2053         direction = -1;
2054     } else {
2055         holdingsColumn = BOARD_WIDTH-1;
2056         countsColumn = BOARD_WIDTH-2;
2057         holdingsStartRow = 0;
2058         direction = 1;
2059     }
2060
2061     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2062         board[i][holdingsColumn] = EmptySquare;
2063         board[i][countsColumn]   = (ChessSquare) 0;
2064     }
2065     while( (p=*holdings++) != NULLCHAR ) {
2066         piece = CharToPiece( ToUpper(p) );
2067         if(piece == EmptySquare) continue;
2068         /*j = (int) piece - (int) WhitePawn;*/
2069         j = PieceToNumber(piece);
2070         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2071         if(j < 0) continue;               /* should not happen */
2072         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2073         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2074         board[holdingsStartRow+j*direction][countsColumn]++;
2075     }
2076 }
2077
2078
2079 void
2080 VariantSwitch(Board board, VariantClass newVariant)
2081 {
2082    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2083    static Board oldBoard;
2084
2085    startedFromPositionFile = FALSE;
2086    if(gameInfo.variant == newVariant) return;
2087
2088    /* [HGM] This routine is called each time an assignment is made to
2089     * gameInfo.variant during a game, to make sure the board sizes
2090     * are set to match the new variant. If that means adding or deleting
2091     * holdings, we shift the playing board accordingly
2092     * This kludge is needed because in ICS observe mode, we get boards
2093     * of an ongoing game without knowing the variant, and learn about the
2094     * latter only later. This can be because of the move list we requested,
2095     * in which case the game history is refilled from the beginning anyway,
2096     * but also when receiving holdings of a crazyhouse game. In the latter
2097     * case we want to add those holdings to the already received position.
2098     */
2099
2100
2101    if (appData.debugMode) {
2102      fprintf(debugFP, "Switch board from %s to %s\n",
2103              VariantName(gameInfo.variant), VariantName(newVariant));
2104      setbuf(debugFP, NULL);
2105    }
2106    shuffleOpenings = 0;       /* [HGM] shuffle */
2107    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2108    switch(newVariant)
2109      {
2110      case VariantShogi:
2111        newWidth = 9;  newHeight = 9;
2112        gameInfo.holdingsSize = 7;
2113      case VariantBughouse:
2114      case VariantCrazyhouse:
2115        newHoldingsWidth = 2; break;
2116      case VariantGreat:
2117        newWidth = 10;
2118      case VariantSuper:
2119        newHoldingsWidth = 2;
2120        gameInfo.holdingsSize = 8;
2121        break;
2122      case VariantGothic:
2123      case VariantCapablanca:
2124      case VariantCapaRandom:
2125        newWidth = 10;
2126      default:
2127        newHoldingsWidth = gameInfo.holdingsSize = 0;
2128      };
2129
2130    if(newWidth  != gameInfo.boardWidth  ||
2131       newHeight != gameInfo.boardHeight ||
2132       newHoldingsWidth != gameInfo.holdingsWidth ) {
2133
2134      /* shift position to new playing area, if needed */
2135      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2136        for(i=0; i<BOARD_HEIGHT; i++)
2137          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2138            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2139              board[i][j];
2140        for(i=0; i<newHeight; i++) {
2141          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2142          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2143        }
2144      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2145        for(i=0; i<BOARD_HEIGHT; i++)
2146          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2147            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2148              board[i][j];
2149      }
2150      gameInfo.boardWidth  = newWidth;
2151      gameInfo.boardHeight = newHeight;
2152      gameInfo.holdingsWidth = newHoldingsWidth;
2153      gameInfo.variant = newVariant;
2154      InitDrawingSizes(-2, 0);
2155    } else gameInfo.variant = newVariant;
2156    CopyBoard(oldBoard, board);   // remember correctly formatted board
2157      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2158    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2159 }
2160
2161 static int loggedOn = FALSE;
2162
2163 /*-- Game start info cache: --*/
2164 int gs_gamenum;
2165 char gs_kind[MSG_SIZ];
2166 static char player1Name[128] = "";
2167 static char player2Name[128] = "";
2168 static char cont_seq[] = "\n\\   ";
2169 static int player1Rating = -1;
2170 static int player2Rating = -1;
2171 /*----------------------------*/
2172
2173 ColorClass curColor = ColorNormal;
2174 int suppressKibitz = 0;
2175
2176 // [HGM] seekgraph
2177 Boolean soughtPending = FALSE;
2178 Boolean seekGraphUp;
2179 #define MAX_SEEK_ADS 200
2180 #define SQUARE 0x80
2181 char *seekAdList[MAX_SEEK_ADS];
2182 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2183 float tcList[MAX_SEEK_ADS];
2184 char colorList[MAX_SEEK_ADS];
2185 int nrOfSeekAds = 0;
2186 int minRating = 1010, maxRating = 2800;
2187 int hMargin = 10, vMargin = 20, h, w;
2188 extern int squareSize, lineGap;
2189
2190 void
2191 PlotSeekAd(int i)
2192 {
2193         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2194         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2195         if(r < minRating+100 && r >=0 ) r = minRating+100;
2196         if(r > maxRating) r = maxRating;
2197         if(tc < 1.) tc = 1.;
2198         if(tc > 95.) tc = 95.;
2199         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2200         y = ((double)r - minRating)/(maxRating - minRating)
2201             * (h-vMargin-squareSize/8-1) + vMargin;
2202         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2203         if(strstr(seekAdList[i], " u ")) color = 1;
2204         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2205            !strstr(seekAdList[i], "bullet") &&
2206            !strstr(seekAdList[i], "blitz") &&
2207            !strstr(seekAdList[i], "standard") ) color = 2;
2208         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2209         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2210 }
2211
2212 void
2213 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2214 {
2215         char buf[MSG_SIZ], *ext = "";
2216         VariantClass v = StringToVariant(type);
2217         if(strstr(type, "wild")) {
2218             ext = type + 4; // append wild number
2219             if(v == VariantFischeRandom) type = "chess960"; else
2220             if(v == VariantLoadable) type = "setup"; else
2221             type = VariantName(v);
2222         }
2223         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2224         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2225             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2226             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2227             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2228             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2229             seekNrList[nrOfSeekAds] = nr;
2230             zList[nrOfSeekAds] = 0;
2231             seekAdList[nrOfSeekAds++] = StrSave(buf);
2232             if(plot) PlotSeekAd(nrOfSeekAds-1);
2233         }
2234 }
2235
2236 void
2237 EraseSeekDot(int i)
2238 {
2239     int x = xList[i], y = yList[i], d=squareSize/4, k;
2240     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2241     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2242     // now replot every dot that overlapped
2243     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2244         int xx = xList[k], yy = yList[k];
2245         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2246             DrawSeekDot(xx, yy, colorList[k]);
2247     }
2248 }
2249
2250 void
2251 RemoveSeekAd(int nr)
2252 {
2253         int i;
2254         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2255             EraseSeekDot(i);
2256             if(seekAdList[i]) free(seekAdList[i]);
2257             seekAdList[i] = seekAdList[--nrOfSeekAds];
2258             seekNrList[i] = seekNrList[nrOfSeekAds];
2259             ratingList[i] = ratingList[nrOfSeekAds];
2260             colorList[i]  = colorList[nrOfSeekAds];
2261             tcList[i] = tcList[nrOfSeekAds];
2262             xList[i]  = xList[nrOfSeekAds];
2263             yList[i]  = yList[nrOfSeekAds];
2264             zList[i]  = zList[nrOfSeekAds];
2265             seekAdList[nrOfSeekAds] = NULL;
2266             break;
2267         }
2268 }
2269
2270 Boolean
2271 MatchSoughtLine(char *line)
2272 {
2273     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2274     int nr, base, inc, u=0; char dummy;
2275
2276     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2277        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2278        (u=1) &&
2279        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2281         // match: compact and save the line
2282         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2283         return TRUE;
2284     }
2285     return FALSE;
2286 }
2287
2288 int
2289 DrawSeekGraph()
2290 {
2291     int i;
2292     if(!seekGraphUp) return FALSE;
2293     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2294     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2295
2296     DrawSeekBackground(0, 0, w, h);
2297     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2298     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2299     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2300         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2301         yy = h-1-yy;
2302         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2303         if(i%500 == 0) {
2304             char buf[MSG_SIZ];
2305             snprintf(buf, MSG_SIZ, "%d", i);
2306             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2307         }
2308     }
2309     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2310     for(i=1; i<100; i+=(i<10?1:5)) {
2311         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2312         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2313         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2314             char buf[MSG_SIZ];
2315             snprintf(buf, MSG_SIZ, "%d", i);
2316             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2317         }
2318     }
2319     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2320     return TRUE;
2321 }
2322
2323 int SeekGraphClick(ClickType click, int x, int y, int moving)
2324 {
2325     static int lastDown = 0, displayed = 0, lastSecond;
2326     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2327         if(click == Release || moving) return FALSE;
2328         nrOfSeekAds = 0;
2329         soughtPending = TRUE;
2330         SendToICS(ics_prefix);
2331         SendToICS("sought\n"); // should this be "sought all"?
2332     } else { // issue challenge based on clicked ad
2333         int dist = 10000; int i, closest = 0, second = 0;
2334         for(i=0; i<nrOfSeekAds; i++) {
2335             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2336             if(d < dist) { dist = d; closest = i; }
2337             second += (d - zList[i] < 120); // count in-range ads
2338             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2339         }
2340         if(dist < 120) {
2341             char buf[MSG_SIZ];
2342             second = (second > 1);
2343             if(displayed != closest || second != lastSecond) {
2344                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2345                 lastSecond = second; displayed = closest;
2346             }
2347             if(click == Press) {
2348                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2349                 lastDown = closest;
2350                 return TRUE;
2351             } // on press 'hit', only show info
2352             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2353             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2354             SendToICS(ics_prefix);
2355             SendToICS(buf);
2356             return TRUE; // let incoming board of started game pop down the graph
2357         } else if(click == Release) { // release 'miss' is ignored
2358             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2359             if(moving == 2) { // right up-click
2360                 nrOfSeekAds = 0; // refresh graph
2361                 soughtPending = TRUE;
2362                 SendToICS(ics_prefix);
2363                 SendToICS("sought\n"); // should this be "sought all"?
2364             }
2365             return TRUE;
2366         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2367         // press miss or release hit 'pop down' seek graph
2368         seekGraphUp = FALSE;
2369         DrawPosition(TRUE, NULL);
2370     }
2371     return TRUE;
2372 }
2373
2374 void
2375 read_from_ics(isr, closure, data, count, error)
2376      InputSourceRef isr;
2377      VOIDSTAR closure;
2378      char *data;
2379      int count;
2380      int error;
2381 {
2382 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2383 #define STARTED_NONE 0
2384 #define STARTED_MOVES 1
2385 #define STARTED_BOARD 2
2386 #define STARTED_OBSERVE 3
2387 #define STARTED_HOLDINGS 4
2388 #define STARTED_CHATTER 5
2389 #define STARTED_COMMENT 6
2390 #define STARTED_MOVES_NOHIDE 7
2391
2392     static int started = STARTED_NONE;
2393     static char parse[20000];
2394     static int parse_pos = 0;
2395     static char buf[BUF_SIZE + 1];
2396     static int firstTime = TRUE, intfSet = FALSE;
2397     static ColorClass prevColor = ColorNormal;
2398     static int savingComment = FALSE;
2399     static int cmatch = 0; // continuation sequence match
2400     char *bp;
2401     char str[MSG_SIZ];
2402     int i, oldi;
2403     int buf_len;
2404     int next_out;
2405     int tkind;
2406     int backup;    /* [DM] For zippy color lines */
2407     char *p;
2408     char talker[MSG_SIZ]; // [HGM] chat
2409     int channel;
2410
2411     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2412
2413     if (appData.debugMode) {
2414       if (!error) {
2415         fprintf(debugFP, "<ICS: ");
2416         show_bytes(debugFP, data, count);
2417         fprintf(debugFP, "\n");
2418       }
2419     }
2420
2421     if (appData.debugMode) { int f = forwardMostMove;
2422         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2423                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2424                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2425     }
2426     if (count > 0) {
2427         /* If last read ended with a partial line that we couldn't parse,
2428            prepend it to the new read and try again. */
2429         if (leftover_len > 0) {
2430             for (i=0; i<leftover_len; i++)
2431               buf[i] = buf[leftover_start + i];
2432         }
2433
2434     /* copy new characters into the buffer */
2435     bp = buf + leftover_len;
2436     buf_len=leftover_len;
2437     for (i=0; i<count; i++)
2438     {
2439         // ignore these
2440         if (data[i] == '\r')
2441             continue;
2442
2443         // join lines split by ICS?
2444         if (!appData.noJoin)
2445         {
2446             /*
2447                 Joining just consists of finding matches against the
2448                 continuation sequence, and discarding that sequence
2449                 if found instead of copying it.  So, until a match
2450                 fails, there's nothing to do since it might be the
2451                 complete sequence, and thus, something we don't want
2452                 copied.
2453             */
2454             if (data[i] == cont_seq[cmatch])
2455             {
2456                 cmatch++;
2457                 if (cmatch == strlen(cont_seq))
2458                 {
2459                     cmatch = 0; // complete match.  just reset the counter
2460
2461                     /*
2462                         it's possible for the ICS to not include the space
2463                         at the end of the last word, making our [correct]
2464                         join operation fuse two separate words.  the server
2465                         does this when the space occurs at the width setting.
2466                     */
2467                     if (!buf_len || buf[buf_len-1] != ' ')
2468                     {
2469                         *bp++ = ' ';
2470                         buf_len++;
2471                     }
2472                 }
2473                 continue;
2474             }
2475             else if (cmatch)
2476             {
2477                 /*
2478                     match failed, so we have to copy what matched before
2479                     falling through and copying this character.  In reality,
2480                     this will only ever be just the newline character, but
2481                     it doesn't hurt to be precise.
2482                 */
2483                 strncpy(bp, cont_seq, cmatch);
2484                 bp += cmatch;
2485                 buf_len += cmatch;
2486                 cmatch = 0;
2487             }
2488         }
2489
2490         // copy this char
2491         *bp++ = data[i];
2492         buf_len++;
2493     }
2494
2495         buf[buf_len] = NULLCHAR;
2496 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2497         next_out = 0;
2498         leftover_start = 0;
2499
2500         i = 0;
2501         while (i < buf_len) {
2502             /* Deal with part of the TELNET option negotiation
2503                protocol.  We refuse to do anything beyond the
2504                defaults, except that we allow the WILL ECHO option,
2505                which ICS uses to turn off password echoing when we are
2506                directly connected to it.  We reject this option
2507                if localLineEditing mode is on (always on in xboard)
2508                and we are talking to port 23, which might be a real
2509                telnet server that will try to keep WILL ECHO on permanently.
2510              */
2511             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2512                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2513                 unsigned char option;
2514                 oldi = i;
2515                 switch ((unsigned char) buf[++i]) {
2516                   case TN_WILL:
2517                     if (appData.debugMode)
2518                       fprintf(debugFP, "\n<WILL ");
2519                     switch (option = (unsigned char) buf[++i]) {
2520                       case TN_ECHO:
2521                         if (appData.debugMode)
2522                           fprintf(debugFP, "ECHO ");
2523                         /* Reply only if this is a change, according
2524                            to the protocol rules. */
2525                         if (remoteEchoOption) break;
2526                         if (appData.localLineEditing &&
2527                             atoi(appData.icsPort) == TN_PORT) {
2528                             TelnetRequest(TN_DONT, TN_ECHO);
2529                         } else {
2530                             EchoOff();
2531                             TelnetRequest(TN_DO, TN_ECHO);
2532                             remoteEchoOption = TRUE;
2533                         }
2534                         break;
2535                       default:
2536                         if (appData.debugMode)
2537                           fprintf(debugFP, "%d ", option);
2538                         /* Whatever this is, we don't want it. */
2539                         TelnetRequest(TN_DONT, option);
2540                         break;
2541                     }
2542                     break;
2543                   case TN_WONT:
2544                     if (appData.debugMode)
2545                       fprintf(debugFP, "\n<WONT ");
2546                     switch (option = (unsigned char) buf[++i]) {
2547                       case TN_ECHO:
2548                         if (appData.debugMode)
2549                           fprintf(debugFP, "ECHO ");
2550                         /* Reply only if this is a change, according
2551                            to the protocol rules. */
2552                         if (!remoteEchoOption) break;
2553                         EchoOn();
2554                         TelnetRequest(TN_DONT, TN_ECHO);
2555                         remoteEchoOption = FALSE;
2556                         break;
2557                       default:
2558                         if (appData.debugMode)
2559                           fprintf(debugFP, "%d ", (unsigned char) option);
2560                         /* Whatever this is, it must already be turned
2561                            off, because we never agree to turn on
2562                            anything non-default, so according to the
2563                            protocol rules, we don't reply. */
2564                         break;
2565                     }
2566                     break;
2567                   case TN_DO:
2568                     if (appData.debugMode)
2569                       fprintf(debugFP, "\n<DO ");
2570                     switch (option = (unsigned char) buf[++i]) {
2571                       default:
2572                         /* Whatever this is, we refuse to do it. */
2573                         if (appData.debugMode)
2574                           fprintf(debugFP, "%d ", option);
2575                         TelnetRequest(TN_WONT, option);
2576                         break;
2577                     }
2578                     break;
2579                   case TN_DONT:
2580                     if (appData.debugMode)
2581                       fprintf(debugFP, "\n<DONT ");
2582                     switch (option = (unsigned char) buf[++i]) {
2583                       default:
2584                         if (appData.debugMode)
2585                           fprintf(debugFP, "%d ", option);
2586                         /* Whatever this is, we are already not doing
2587                            it, because we never agree to do anything
2588                            non-default, so according to the protocol
2589                            rules, we don't reply. */
2590                         break;
2591                     }
2592                     break;
2593                   case TN_IAC:
2594                     if (appData.debugMode)
2595                       fprintf(debugFP, "\n<IAC ");
2596                     /* Doubled IAC; pass it through */
2597                     i--;
2598                     break;
2599                   default:
2600                     if (appData.debugMode)
2601                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2602                     /* Drop all other telnet commands on the floor */
2603                     break;
2604                 }
2605                 if (oldi > next_out)
2606                   SendToPlayer(&buf[next_out], oldi - next_out);
2607                 if (++i > next_out)
2608                   next_out = i;
2609                 continue;
2610             }
2611
2612             /* OK, this at least will *usually* work */
2613             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2614                 loggedOn = TRUE;
2615             }
2616
2617             if (loggedOn && !intfSet) {
2618                 if (ics_type == ICS_ICC) {
2619                   snprintf(str, MSG_SIZ,
2620                           "/set-quietly interface %s\n/set-quietly style 12\n",
2621                           programVersion);
2622                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2624                 } else if (ics_type == ICS_CHESSNET) {
2625                   snprintf(str, MSG_SIZ, "/style 12\n");
2626                 } else {
2627                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2628                   strcat(str, programVersion);
2629                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2630                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2631                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2632 #ifdef WIN32
2633                   strcat(str, "$iset nohighlight 1\n");
2634 #endif
2635                   strcat(str, "$iset lock 1\n$style 12\n");
2636                 }
2637                 SendToICS(str);
2638                 NotifyFrontendLogin();
2639                 intfSet = TRUE;
2640             }
2641
2642             if (started == STARTED_COMMENT) {
2643                 /* Accumulate characters in comment */
2644                 parse[parse_pos++] = buf[i];
2645                 if (buf[i] == '\n') {
2646                     parse[parse_pos] = NULLCHAR;
2647                     if(chattingPartner>=0) {
2648                         char mess[MSG_SIZ];
2649                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2650                         OutputChatMessage(chattingPartner, mess);
2651                         chattingPartner = -1;
2652                         next_out = i+1; // [HGM] suppress printing in ICS window
2653                     } else
2654                     if(!suppressKibitz) // [HGM] kibitz
2655                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2656                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2657                         int nrDigit = 0, nrAlph = 0, j;
2658                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2659                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2660                         parse[parse_pos] = NULLCHAR;
2661                         // try to be smart: if it does not look like search info, it should go to
2662                         // ICS interaction window after all, not to engine-output window.
2663                         for(j=0; j<parse_pos; j++) { // count letters and digits
2664                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2665                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2666                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2667                         }
2668                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2669                             int depth=0; float score;
2670                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2671                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2672                                 pvInfoList[forwardMostMove-1].depth = depth;
2673                                 pvInfoList[forwardMostMove-1].score = 100*score;
2674                             }
2675                             OutputKibitz(suppressKibitz, parse);
2676                         } else {
2677                             char tmp[MSG_SIZ];
2678                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2679                             SendToPlayer(tmp, strlen(tmp));
2680                         }
2681                         next_out = i+1; // [HGM] suppress printing in ICS window
2682                     }
2683                     started = STARTED_NONE;
2684                 } else {
2685                     /* Don't match patterns against characters in comment */
2686                     i++;
2687                     continue;
2688                 }
2689             }
2690             if (started == STARTED_CHATTER) {
2691                 if (buf[i] != '\n') {
2692                     /* Don't match patterns against characters in chatter */
2693                     i++;
2694                     continue;
2695                 }
2696                 started = STARTED_NONE;
2697                 if(suppressKibitz) next_out = i+1;
2698             }
2699
2700             /* Kludge to deal with rcmd protocol */
2701             if (firstTime && looking_at(buf, &i, "\001*")) {
2702                 DisplayFatalError(&buf[1], 0, 1);
2703                 continue;
2704             } else {
2705                 firstTime = FALSE;
2706             }
2707
2708             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2709                 ics_type = ICS_ICC;
2710                 ics_prefix = "/";
2711                 if (appData.debugMode)
2712                   fprintf(debugFP, "ics_type %d\n", ics_type);
2713                 continue;
2714             }
2715             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2716                 ics_type = ICS_FICS;
2717                 ics_prefix = "$";
2718                 if (appData.debugMode)
2719                   fprintf(debugFP, "ics_type %d\n", ics_type);
2720                 continue;
2721             }
2722             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2723                 ics_type = ICS_CHESSNET;
2724                 ics_prefix = "/";
2725                 if (appData.debugMode)
2726                   fprintf(debugFP, "ics_type %d\n", ics_type);
2727                 continue;
2728             }
2729
2730             if (!loggedOn &&
2731                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2732                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2733                  looking_at(buf, &i, "will be \"*\""))) {
2734               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2735               continue;
2736             }
2737
2738             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2739               char buf[MSG_SIZ];
2740               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2741               DisplayIcsInteractionTitle(buf);
2742               have_set_title = TRUE;
2743             }
2744
2745             /* skip finger notes */
2746             if (started == STARTED_NONE &&
2747                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2748                  (buf[i] == '1' && buf[i+1] == '0')) &&
2749                 buf[i+2] == ':' && buf[i+3] == ' ') {
2750               started = STARTED_CHATTER;
2751               i += 3;
2752               continue;
2753             }
2754
2755             oldi = i;
2756             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2757             if(appData.seekGraph) {
2758                 if(soughtPending && MatchSoughtLine(buf+i)) {
2759                     i = strstr(buf+i, "rated") - buf;
2760                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2761                     next_out = leftover_start = i;
2762                     started = STARTED_CHATTER;
2763                     suppressKibitz = TRUE;
2764                     continue;
2765                 }
2766                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2767                         && looking_at(buf, &i, "* ads displayed")) {
2768                     soughtPending = FALSE;
2769                     seekGraphUp = TRUE;
2770                     DrawSeekGraph();
2771                     continue;
2772                 }
2773                 if(appData.autoRefresh) {
2774                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2775                         int s = (ics_type == ICS_ICC); // ICC format differs
2776                         if(seekGraphUp)
2777                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2778                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2779                         looking_at(buf, &i, "*% "); // eat prompt
2780                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2781                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2782                         next_out = i; // suppress
2783                         continue;
2784                     }
2785                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2786                         char *p = star_match[0];
2787                         while(*p) {
2788                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2789                             while(*p && *p++ != ' '); // next
2790                         }
2791                         looking_at(buf, &i, "*% "); // eat prompt
2792                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2793                         next_out = i;
2794                         continue;
2795                     }
2796                 }
2797             }
2798
2799             /* skip formula vars */
2800             if (started == STARTED_NONE &&
2801                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2802               started = STARTED_CHATTER;
2803               i += 3;
2804               continue;
2805             }
2806
2807             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2808             if (appData.autoKibitz && started == STARTED_NONE &&
2809                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2810                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2811                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2812                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2813                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2814                         suppressKibitz = TRUE;
2815                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2816                         next_out = i;
2817                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2818                                 && (gameMode == IcsPlayingWhite)) ||
2819                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2820                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2821                             started = STARTED_CHATTER; // own kibitz we simply discard
2822                         else {
2823                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2824                             parse_pos = 0; parse[0] = NULLCHAR;
2825                             savingComment = TRUE;
2826                             suppressKibitz = gameMode != IcsObserving ? 2 :
2827                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2828                         }
2829                         continue;
2830                 } else
2831                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2832                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2833                          && atoi(star_match[0])) {
2834                     // suppress the acknowledgements of our own autoKibitz
2835                     char *p;
2836                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2837                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2838                     SendToPlayer(star_match[0], strlen(star_match[0]));
2839                     if(looking_at(buf, &i, "*% ")) // eat prompt
2840                         suppressKibitz = FALSE;
2841                     next_out = i;
2842                     continue;
2843                 }
2844             } // [HGM] kibitz: end of patch
2845
2846             // [HGM] chat: intercept tells by users for which we have an open chat window
2847             channel = -1;
2848             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2849                                            looking_at(buf, &i, "* whispers:") ||
2850                                            looking_at(buf, &i, "* kibitzes:") ||
2851                                            looking_at(buf, &i, "* shouts:") ||
2852                                            looking_at(buf, &i, "* c-shouts:") ||
2853                                            looking_at(buf, &i, "--> * ") ||
2854                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2855                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2856                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2857                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2858                 int p;
2859                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2860                 chattingPartner = -1;
2861
2862                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2863                 for(p=0; p<MAX_CHAT; p++) {
2864                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2865                     talker[0] = '['; strcat(talker, "] ");
2866                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2867                     chattingPartner = p; break;
2868                     }
2869                 } else
2870                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2871                 for(p=0; p<MAX_CHAT; p++) {
2872                     if(!strcmp("kibitzes", chatPartner[p])) {
2873                         talker[0] = '['; strcat(talker, "] ");
2874                         chattingPartner = p; break;
2875                     }
2876                 } else
2877                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2878                 for(p=0; p<MAX_CHAT; p++) {
2879                     if(!strcmp("whispers", chatPartner[p])) {
2880                         talker[0] = '['; strcat(talker, "] ");
2881                         chattingPartner = p; break;
2882                     }
2883                 } else
2884                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2885                   if(buf[i-8] == '-' && buf[i-3] == 't')
2886                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2887                     if(!strcmp("c-shouts", chatPartner[p])) {
2888                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2889                         chattingPartner = p; break;
2890                     }
2891                   }
2892                   if(chattingPartner < 0)
2893                   for(p=0; p<MAX_CHAT; p++) {
2894                     if(!strcmp("shouts", chatPartner[p])) {
2895                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2896                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2897                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2898                         chattingPartner = p; break;
2899                     }
2900                   }
2901                 }
2902                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2903                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2904                     talker[0] = 0; Colorize(ColorTell, FALSE);
2905                     chattingPartner = p; break;
2906                 }
2907                 if(chattingPartner<0) i = oldi; else {
2908                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2909                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2910                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2911                     started = STARTED_COMMENT;
2912                     parse_pos = 0; parse[0] = NULLCHAR;
2913                     savingComment = 3 + chattingPartner; // counts as TRUE
2914                     suppressKibitz = TRUE;
2915                     continue;
2916                 }
2917             } // [HGM] chat: end of patch
2918
2919             if (appData.zippyTalk || appData.zippyPlay) {
2920                 /* [DM] Backup address for color zippy lines */
2921                 backup = i;
2922 #if ZIPPY
2923                if (loggedOn == TRUE)
2924                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2925                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2926 #endif
2927             } // [DM] 'else { ' deleted
2928                 if (
2929                     /* Regular tells and says */
2930                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2931                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2932                     looking_at(buf, &i, "* says: ") ||
2933                     /* Don't color "message" or "messages" output */
2934                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2935                     looking_at(buf, &i, "*. * at *:*: ") ||
2936                     looking_at(buf, &i, "--* (*:*): ") ||
2937                     /* Message notifications (same color as tells) */
2938                     looking_at(buf, &i, "* has left a message ") ||
2939                     looking_at(buf, &i, "* just sent you a message:\n") ||
2940                     /* Whispers and kibitzes */
2941                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2942                     looking_at(buf, &i, "* kibitzes: ") ||
2943                     /* Channel tells */
2944                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2945
2946                   if (tkind == 1 && strchr(star_match[0], ':')) {
2947                       /* Avoid "tells you:" spoofs in channels */
2948                      tkind = 3;
2949                   }
2950                   if (star_match[0][0] == NULLCHAR ||
2951                       strchr(star_match[0], ' ') ||
2952                       (tkind == 3 && strchr(star_match[1], ' '))) {
2953                     /* Reject bogus matches */
2954                     i = oldi;
2955                   } else {
2956                     if (appData.colorize) {
2957                       if (oldi > next_out) {
2958                         SendToPlayer(&buf[next_out], oldi - next_out);
2959                         next_out = oldi;
2960                       }
2961                       switch (tkind) {
2962                       case 1:
2963                         Colorize(ColorTell, FALSE);
2964                         curColor = ColorTell;
2965                         break;
2966                       case 2:
2967                         Colorize(ColorKibitz, FALSE);
2968                         curColor = ColorKibitz;
2969                         break;
2970                       case 3:
2971                         p = strrchr(star_match[1], '(');
2972                         if (p == NULL) {
2973                           p = star_match[1];
2974                         } else {
2975                           p++;
2976                         }
2977                         if (atoi(p) == 1) {
2978                           Colorize(ColorChannel1, FALSE);
2979                           curColor = ColorChannel1;
2980                         } else {
2981                           Colorize(ColorChannel, FALSE);
2982                           curColor = ColorChannel;
2983                         }
2984                         break;
2985                       case 5:
2986                         curColor = ColorNormal;
2987                         break;
2988                       }
2989                     }
2990                     if (started == STARTED_NONE && appData.autoComment &&
2991                         (gameMode == IcsObserving ||
2992                          gameMode == IcsPlayingWhite ||
2993                          gameMode == IcsPlayingBlack)) {
2994                       parse_pos = i - oldi;
2995                       memcpy(parse, &buf[oldi], parse_pos);
2996                       parse[parse_pos] = NULLCHAR;
2997                       started = STARTED_COMMENT;
2998                       savingComment = TRUE;
2999                     } else {
3000                       started = STARTED_CHATTER;
3001                       savingComment = FALSE;
3002                     }
3003                     loggedOn = TRUE;
3004                     continue;
3005                   }
3006                 }
3007
3008                 if (looking_at(buf, &i, "* s-shouts: ") ||
3009                     looking_at(buf, &i, "* c-shouts: ")) {
3010                     if (appData.colorize) {
3011                         if (oldi > next_out) {
3012                             SendToPlayer(&buf[next_out], oldi - next_out);
3013                             next_out = oldi;
3014                         }
3015                         Colorize(ColorSShout, FALSE);
3016                         curColor = ColorSShout;
3017                     }
3018                     loggedOn = TRUE;
3019                     started = STARTED_CHATTER;
3020                     continue;
3021                 }
3022
3023                 if (looking_at(buf, &i, "--->")) {
3024                     loggedOn = TRUE;
3025                     continue;
3026                 }
3027
3028                 if (looking_at(buf, &i, "* shouts: ") ||
3029                     looking_at(buf, &i, "--> ")) {
3030                     if (appData.colorize) {
3031                         if (oldi > next_out) {
3032                             SendToPlayer(&buf[next_out], oldi - next_out);
3033                             next_out = oldi;
3034                         }
3035                         Colorize(ColorShout, FALSE);
3036                         curColor = ColorShout;
3037                     }
3038                     loggedOn = TRUE;
3039                     started = STARTED_CHATTER;
3040                     continue;
3041                 }
3042
3043                 if (looking_at( buf, &i, "Challenge:")) {
3044                     if (appData.colorize) {
3045                         if (oldi > next_out) {
3046                             SendToPlayer(&buf[next_out], oldi - next_out);
3047                             next_out = oldi;
3048                         }
3049                         Colorize(ColorChallenge, FALSE);
3050                         curColor = ColorChallenge;
3051                     }
3052                     loggedOn = TRUE;
3053                     continue;
3054                 }
3055
3056                 if (looking_at(buf, &i, "* offers you") ||
3057                     looking_at(buf, &i, "* offers to be") ||
3058                     looking_at(buf, &i, "* would like to") ||
3059                     looking_at(buf, &i, "* requests to") ||
3060                     looking_at(buf, &i, "Your opponent offers") ||
3061                     looking_at(buf, &i, "Your opponent requests")) {
3062
3063                     if (appData.colorize) {
3064                         if (oldi > next_out) {
3065                             SendToPlayer(&buf[next_out], oldi - next_out);
3066                             next_out = oldi;
3067                         }
3068                         Colorize(ColorRequest, FALSE);
3069                         curColor = ColorRequest;
3070                     }
3071                     continue;
3072                 }
3073
3074                 if (looking_at(buf, &i, "* (*) seeking")) {
3075                     if (appData.colorize) {
3076                         if (oldi > next_out) {
3077                             SendToPlayer(&buf[next_out], oldi - next_out);
3078                             next_out = oldi;
3079                         }
3080                         Colorize(ColorSeek, FALSE);
3081                         curColor = ColorSeek;
3082                     }
3083                     continue;
3084             }
3085
3086             if (looking_at(buf, &i, "\\   ")) {
3087                 if (prevColor != ColorNormal) {
3088                     if (oldi > next_out) {
3089                         SendToPlayer(&buf[next_out], oldi - next_out);
3090                         next_out = oldi;
3091                     }
3092                     Colorize(prevColor, TRUE);
3093                     curColor = prevColor;
3094                 }
3095                 if (savingComment) {
3096                     parse_pos = i - oldi;
3097                     memcpy(parse, &buf[oldi], parse_pos);
3098                     parse[parse_pos] = NULLCHAR;
3099                     started = STARTED_COMMENT;
3100                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3101                         chattingPartner = savingComment - 3; // kludge to remember the box
3102                 } else {
3103                     started = STARTED_CHATTER;
3104                 }
3105                 continue;
3106             }
3107
3108             if (looking_at(buf, &i, "Black Strength :") ||
3109                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3110                 looking_at(buf, &i, "<10>") ||
3111                 looking_at(buf, &i, "#@#")) {
3112                 /* Wrong board style */
3113                 loggedOn = TRUE;
3114                 SendToICS(ics_prefix);
3115                 SendToICS("set style 12\n");
3116                 SendToICS(ics_prefix);
3117                 SendToICS("refresh\n");
3118                 continue;
3119             }
3120
3121             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3122                 ICSInitScript();
3123                 have_sent_ICS_logon = 1;
3124                 continue;
3125             }
3126
3127             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3128                 (looking_at(buf, &i, "\n<12> ") ||
3129                  looking_at(buf, &i, "<12> "))) {
3130                 loggedOn = TRUE;
3131                 if (oldi > next_out) {
3132                     SendToPlayer(&buf[next_out], oldi - next_out);
3133                 }
3134                 next_out = i;
3135                 started = STARTED_BOARD;
3136                 parse_pos = 0;
3137                 continue;
3138             }
3139
3140             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3141                 looking_at(buf, &i, "<b1> ")) {
3142                 if (oldi > next_out) {
3143                     SendToPlayer(&buf[next_out], oldi - next_out);
3144                 }
3145                 next_out = i;
3146                 started = STARTED_HOLDINGS;
3147                 parse_pos = 0;
3148                 continue;
3149             }
3150
3151             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3152                 loggedOn = TRUE;
3153                 /* Header for a move list -- first line */
3154
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     switch (gameMode) {
3158                       case IcsIdle:
3159                       case BeginningOfGame:
3160                         /* User typed "moves" or "oldmoves" while we
3161                            were idle.  Pretend we asked for these
3162                            moves and soak them up so user can step
3163                            through them and/or save them.
3164                            */
3165                         Reset(FALSE, TRUE);
3166                         gameMode = IcsObserving;
3167                         ModeHighlight();
3168                         ics_gamenum = -1;
3169                         ics_getting_history = H_GOT_UNREQ_HEADER;
3170                         break;
3171                       case EditGame: /*?*/
3172                       case EditPosition: /*?*/
3173                         /* Should above feature work in these modes too? */
3174                         /* For now it doesn't */
3175                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3176                         break;
3177                       default:
3178                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3179                         break;
3180                     }
3181                     break;
3182                   case H_REQUESTED:
3183                     /* Is this the right one? */
3184                     if (gameInfo.white && gameInfo.black &&
3185                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3186                         strcmp(gameInfo.black, star_match[2]) == 0) {
3187                         /* All is well */
3188                         ics_getting_history = H_GOT_REQ_HEADER;
3189                     }
3190                     break;
3191                   case H_GOT_REQ_HEADER:
3192                   case H_GOT_UNREQ_HEADER:
3193                   case H_GOT_UNWANTED_HEADER:
3194                   case H_GETTING_MOVES:
3195                     /* Should not happen */
3196                     DisplayError(_("Error gathering move list: two headers"), 0);
3197                     ics_getting_history = H_FALSE;
3198                     break;
3199                 }
3200
3201                 /* Save player ratings into gameInfo if needed */
3202                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3203                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3204                     (gameInfo.whiteRating == -1 ||
3205                      gameInfo.blackRating == -1)) {
3206
3207                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3208                     gameInfo.blackRating = string_to_rating(star_match[3]);
3209                     if (appData.debugMode)
3210                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3211                               gameInfo.whiteRating, gameInfo.blackRating);
3212                 }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i,
3217               "* * match, initial time: * minute*, increment: * second")) {
3218                 /* Header for a move list -- second line */
3219                 /* Initial board will follow if this is a wild game */
3220                 if (gameInfo.event != NULL) free(gameInfo.event);
3221                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3222                 gameInfo.event = StrSave(str);
3223                 /* [HGM] we switched variant. Translate boards if needed. */
3224                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3225                 continue;
3226             }
3227
3228             if (looking_at(buf, &i, "Move  ")) {
3229                 /* Beginning of a move list */
3230                 switch (ics_getting_history) {
3231                   case H_FALSE:
3232                     /* Normally should not happen */
3233                     /* Maybe user hit reset while we were parsing */
3234                     break;
3235                   case H_REQUESTED:
3236                     /* Happens if we are ignoring a move list that is not
3237                      * the one we just requested.  Common if the user
3238                      * tries to observe two games without turning off
3239                      * getMoveList */
3240                     break;
3241                   case H_GETTING_MOVES:
3242                     /* Should not happen */
3243                     DisplayError(_("Error gathering move list: nested"), 0);
3244                     ics_getting_history = H_FALSE;
3245                     break;
3246                   case H_GOT_REQ_HEADER:
3247                     ics_getting_history = H_GETTING_MOVES;
3248                     started = STARTED_MOVES;
3249                     parse_pos = 0;
3250                     if (oldi > next_out) {
3251                         SendToPlayer(&buf[next_out], oldi - next_out);
3252                     }
3253                     break;
3254                   case H_GOT_UNREQ_HEADER:
3255                     ics_getting_history = H_GETTING_MOVES;
3256                     started = STARTED_MOVES_NOHIDE;
3257                     parse_pos = 0;
3258                     break;
3259                   case H_GOT_UNWANTED_HEADER:
3260                     ics_getting_history = H_FALSE;
3261                     break;
3262                 }
3263                 continue;
3264             }
3265
3266             if (looking_at(buf, &i, "% ") ||
3267                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3268                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3269                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3270                     soughtPending = FALSE;
3271                     seekGraphUp = TRUE;
3272                     DrawSeekGraph();
3273                 }
3274                 if(suppressKibitz) next_out = i;
3275                 savingComment = FALSE;
3276                 suppressKibitz = 0;
3277                 switch (started) {
3278                   case STARTED_MOVES:
3279                   case STARTED_MOVES_NOHIDE:
3280                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3281                     parse[parse_pos + i - oldi] = NULLCHAR;
3282                     ParseGameHistory(parse);
3283 #if ZIPPY
3284                     if (appData.zippyPlay && first.initDone) {
3285                         FeedMovesToProgram(&first, forwardMostMove);
3286                         if (gameMode == IcsPlayingWhite) {
3287                             if (WhiteOnMove(forwardMostMove)) {
3288                                 if (first.sendTime) {
3289                                   if (first.useColors) {
3290                                     SendToProgram("black\n", &first);
3291                                   }
3292                                   SendTimeRemaining(&first, TRUE);
3293                                 }
3294                                 if (first.useColors) {
3295                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3296                                 }
3297                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3298                                 first.maybeThinking = TRUE;
3299                             } else {
3300                                 if (first.usePlayother) {
3301                                   if (first.sendTime) {
3302                                     SendTimeRemaining(&first, TRUE);
3303                                   }
3304                                   SendToProgram("playother\n", &first);
3305                                   firstMove = FALSE;
3306                                 } else {
3307                                   firstMove = TRUE;
3308                                 }
3309                             }
3310                         } else if (gameMode == IcsPlayingBlack) {
3311                             if (!WhiteOnMove(forwardMostMove)) {
3312                                 if (first.sendTime) {
3313                                   if (first.useColors) {
3314                                     SendToProgram("white\n", &first);
3315                                   }
3316                                   SendTimeRemaining(&first, FALSE);
3317                                 }
3318                                 if (first.useColors) {
3319                                   SendToProgram("black\n", &first);
3320                                 }
3321                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3322                                 first.maybeThinking = TRUE;
3323                             } else {
3324                                 if (first.usePlayother) {
3325                                   if (first.sendTime) {
3326                                     SendTimeRemaining(&first, FALSE);
3327                                   }
3328                                   SendToProgram("playother\n", &first);
3329                                   firstMove = FALSE;
3330                                 } else {
3331                                   firstMove = TRUE;
3332                                 }
3333                             }
3334                         }
3335                     }
3336 #endif
3337                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3338                         /* Moves came from oldmoves or moves command
3339                            while we weren't doing anything else.
3340                            */
3341                         currentMove = forwardMostMove;
3342                         ClearHighlights();/*!!could figure this out*/
3343                         flipView = appData.flipView;
3344                         DrawPosition(TRUE, boards[currentMove]);
3345                         DisplayBothClocks();
3346                         snprintf(str, MSG_SIZ, "%s vs. %s",
3347                                 gameInfo.white, gameInfo.black);
3348                         DisplayTitle(str);
3349                         gameMode = IcsIdle;
3350                     } else {
3351                         /* Moves were history of an active game */
3352                         if (gameInfo.resultDetails != NULL) {
3353                             free(gameInfo.resultDetails);
3354                             gameInfo.resultDetails = NULL;
3355                         }
3356                     }
3357                     HistorySet(parseList, backwardMostMove,
3358                                forwardMostMove, currentMove-1);
3359                     DisplayMove(currentMove - 1);
3360                     if (started == STARTED_MOVES) next_out = i;
3361                     started = STARTED_NONE;
3362                     ics_getting_history = H_FALSE;
3363                     break;
3364
3365                   case STARTED_OBSERVE:
3366                     started = STARTED_NONE;
3367                     SendToICS(ics_prefix);
3368                     SendToICS("refresh\n");
3369                     break;
3370
3371                   default:
3372                     break;
3373                 }
3374                 if(bookHit) { // [HGM] book: simulate book reply
3375                     static char bookMove[MSG_SIZ]; // a bit generous?
3376
3377                     programStats.nodes = programStats.depth = programStats.time =
3378                     programStats.score = programStats.got_only_move = 0;
3379                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3380
3381                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3382                     strcat(bookMove, bookHit);
3383                     HandleMachineMove(bookMove, &first);
3384                 }
3385                 continue;
3386             }
3387
3388             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3389                  started == STARTED_HOLDINGS ||
3390                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3391                 /* Accumulate characters in move list or board */
3392                 parse[parse_pos++] = buf[i];
3393             }
3394
3395             /* Start of game messages.  Mostly we detect start of game
3396                when the first board image arrives.  On some versions
3397                of the ICS, though, we need to do a "refresh" after starting
3398                to observe in order to get the current board right away. */
3399             if (looking_at(buf, &i, "Adding game * to observation list")) {
3400                 started = STARTED_OBSERVE;
3401                 continue;
3402             }
3403
3404             /* Handle auto-observe */
3405             if (appData.autoObserve &&
3406                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3407                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3408                 char *player;
3409                 /* Choose the player that was highlighted, if any. */
3410                 if (star_match[0][0] == '\033' ||
3411                     star_match[1][0] != '\033') {
3412                     player = star_match[0];
3413                 } else {
3414                     player = star_match[2];
3415                 }
3416                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3417                         ics_prefix, StripHighlightAndTitle(player));
3418                 SendToICS(str);
3419
3420                 /* Save ratings from notify string */
3421                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3422                 player1Rating = string_to_rating(star_match[1]);
3423                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3424                 player2Rating = string_to_rating(star_match[3]);
3425
3426                 if (appData.debugMode)
3427                   fprintf(debugFP,
3428                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3429                           player1Name, player1Rating,
3430                           player2Name, player2Rating);
3431
3432                 continue;
3433             }
3434
3435             /* Deal with automatic examine mode after a game,
3436                and with IcsObserving -> IcsExamining transition */
3437             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3438                 looking_at(buf, &i, "has made you an examiner of game *")) {
3439
3440                 int gamenum = atoi(star_match[0]);
3441                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3442                     gamenum == ics_gamenum) {
3443                     /* We were already playing or observing this game;
3444                        no need to refetch history */
3445                     gameMode = IcsExamining;
3446                     if (pausing) {
3447                         pauseExamForwardMostMove = forwardMostMove;
3448                     } else if (currentMove < forwardMostMove) {
3449                         ForwardInner(forwardMostMove);
3450                     }
3451                 } else {
3452                     /* I don't think this case really can happen */
3453                     SendToICS(ics_prefix);
3454                     SendToICS("refresh\n");
3455                 }
3456                 continue;
3457             }
3458
3459             /* Error messages */
3460 //          if (ics_user_moved) {
3461             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3462                 if (looking_at(buf, &i, "Illegal move") ||
3463                     looking_at(buf, &i, "Not a legal move") ||
3464                     looking_at(buf, &i, "Your king is in check") ||
3465                     looking_at(buf, &i, "It isn't your turn") ||
3466                     looking_at(buf, &i, "It is not your move")) {
3467                     /* Illegal move */
3468                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3469                         currentMove = forwardMostMove-1;
3470                         DisplayMove(currentMove - 1); /* before DMError */
3471                         DrawPosition(FALSE, boards[currentMove]);
3472                         SwitchClocks(forwardMostMove-1); // [HGM] race
3473                         DisplayBothClocks();
3474                     }
3475                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3476                     ics_user_moved = 0;
3477                     continue;
3478                 }
3479             }
3480
3481             if (looking_at(buf, &i, "still have time") ||
3482                 looking_at(buf, &i, "not out of time") ||
3483                 looking_at(buf, &i, "either player is out of time") ||
3484                 looking_at(buf, &i, "has timeseal; checking")) {
3485                 /* We must have called his flag a little too soon */
3486                 whiteFlag = blackFlag = FALSE;
3487                 continue;
3488             }
3489
3490             if (looking_at(buf, &i, "added * seconds to") ||
3491                 looking_at(buf, &i, "seconds were added to")) {
3492                 /* Update the clocks */
3493                 SendToICS(ics_prefix);
3494                 SendToICS("refresh\n");
3495                 continue;
3496             }
3497
3498             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3499                 ics_clock_paused = TRUE;
3500                 StopClocks();
3501                 continue;
3502             }
3503
3504             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3505                 ics_clock_paused = FALSE;
3506                 StartClocks();
3507                 continue;
3508             }
3509
3510             /* Grab player ratings from the Creating: message.
3511                Note we have to check for the special case when
3512                the ICS inserts things like [white] or [black]. */
3513             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3514                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3515                 /* star_matches:
3516                    0    player 1 name (not necessarily white)
3517                    1    player 1 rating
3518                    2    empty, white, or black (IGNORED)
3519                    3    player 2 name (not necessarily black)
3520                    4    player 2 rating
3521
3522                    The names/ratings are sorted out when the game
3523                    actually starts (below).
3524                 */
3525                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3526                 player1Rating = string_to_rating(star_match[1]);
3527                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3528                 player2Rating = string_to_rating(star_match[4]);
3529
3530                 if (appData.debugMode)
3531                   fprintf(debugFP,
3532                           "Ratings from 'Creating:' %s %d, %s %d\n",
3533                           player1Name, player1Rating,
3534                           player2Name, player2Rating);
3535
3536                 continue;
3537             }
3538
3539             /* Improved generic start/end-of-game messages */
3540             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3541                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3542                 /* If tkind == 0: */
3543                 /* star_match[0] is the game number */
3544                 /*           [1] is the white player's name */
3545                 /*           [2] is the black player's name */
3546                 /* For end-of-game: */
3547                 /*           [3] is the reason for the game end */
3548                 /*           [4] is a PGN end game-token, preceded by " " */
3549                 /* For start-of-game: */
3550                 /*           [3] begins with "Creating" or "Continuing" */
3551                 /*           [4] is " *" or empty (don't care). */
3552                 int gamenum = atoi(star_match[0]);
3553                 char *whitename, *blackname, *why, *endtoken;
3554                 ChessMove endtype = EndOfFile;
3555
3556                 if (tkind == 0) {
3557                   whitename = star_match[1];
3558                   blackname = star_match[2];
3559                   why = star_match[3];
3560                   endtoken = star_match[4];
3561                 } else {
3562                   whitename = star_match[1];
3563                   blackname = star_match[3];
3564                   why = star_match[5];
3565                   endtoken = star_match[6];
3566                 }
3567
3568                 /* Game start messages */
3569                 if (strncmp(why, "Creating ", 9) == 0 ||
3570                     strncmp(why, "Continuing ", 11) == 0) {
3571                     gs_gamenum = gamenum;
3572                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3573                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3574 #if ZIPPY
3575                     if (appData.zippyPlay) {
3576                         ZippyGameStart(whitename, blackname);
3577                     }
3578 #endif /*ZIPPY*/
3579                     partnerBoardValid = FALSE; // [HGM] bughouse
3580                     continue;
3581                 }
3582
3583                 /* Game end messages */
3584                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3585                     ics_gamenum != gamenum) {
3586                     continue;
3587                 }
3588                 while (endtoken[0] == ' ') endtoken++;
3589                 switch (endtoken[0]) {
3590                   case '*':
3591                   default:
3592                     endtype = GameUnfinished;
3593                     break;
3594                   case '0':
3595                     endtype = BlackWins;
3596                     break;
3597                   case '1':
3598                     if (endtoken[1] == '/')
3599                       endtype = GameIsDrawn;
3600                     else
3601                       endtype = WhiteWins;
3602                     break;
3603                 }
3604                 GameEnds(endtype, why, GE_ICS);
3605 #if ZIPPY
3606                 if (appData.zippyPlay && first.initDone) {
3607                     ZippyGameEnd(endtype, why);
3608                     if (first.pr == NULL) {
3609                       /* Start the next process early so that we'll
3610                          be ready for the next challenge */
3611                       StartChessProgram(&first);
3612                     }
3613                     /* Send "new" early, in case this command takes
3614                        a long time to finish, so that we'll be ready
3615                        for the next challenge. */
3616                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3617                     Reset(TRUE, TRUE);
3618                 }
3619 #endif /*ZIPPY*/
3620                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3621                 continue;
3622             }
3623
3624             if (looking_at(buf, &i, "Removing game * from observation") ||
3625                 looking_at(buf, &i, "no longer observing game *") ||
3626                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3627                 if (gameMode == IcsObserving &&
3628                     atoi(star_match[0]) == ics_gamenum)
3629                   {
3630                       /* icsEngineAnalyze */
3631                       if (appData.icsEngineAnalyze) {
3632                             ExitAnalyzeMode();
3633                             ModeHighlight();
3634                       }
3635                       StopClocks();
3636                       gameMode = IcsIdle;
3637                       ics_gamenum = -1;
3638                       ics_user_moved = FALSE;
3639                   }
3640                 continue;
3641             }
3642
3643             if (looking_at(buf, &i, "no longer examining game *")) {
3644                 if (gameMode == IcsExamining &&
3645                     atoi(star_match[0]) == ics_gamenum)
3646                   {
3647                       gameMode = IcsIdle;
3648                       ics_gamenum = -1;
3649                       ics_user_moved = FALSE;
3650                   }
3651                 continue;
3652             }
3653
3654             /* Advance leftover_start past any newlines we find,
3655                so only partial lines can get reparsed */
3656             if (looking_at(buf, &i, "\n")) {
3657                 prevColor = curColor;
3658                 if (curColor != ColorNormal) {
3659                     if (oldi > next_out) {
3660                         SendToPlayer(&buf[next_out], oldi - next_out);
3661                         next_out = oldi;
3662                     }
3663                     Colorize(ColorNormal, FALSE);
3664                     curColor = ColorNormal;
3665                 }
3666                 if (started == STARTED_BOARD) {
3667                     started = STARTED_NONE;
3668                     parse[parse_pos] = NULLCHAR;
3669                     ParseBoard12(parse);
3670                     ics_user_moved = 0;
3671
3672                     /* Send premove here */
3673                     if (appData.premove) {
3674                       char str[MSG_SIZ];
3675                       if (currentMove == 0 &&
3676                           gameMode == IcsPlayingWhite &&
3677                           appData.premoveWhite) {
3678                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3679                         if (appData.debugMode)
3680                           fprintf(debugFP, "Sending premove:\n");
3681                         SendToICS(str);
3682                       } else if (currentMove == 1 &&
3683                                  gameMode == IcsPlayingBlack &&
3684                                  appData.premoveBlack) {
3685                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3686                         if (appData.debugMode)
3687                           fprintf(debugFP, "Sending premove:\n");
3688                         SendToICS(str);
3689                       } else if (gotPremove) {
3690                         gotPremove = 0;
3691                         ClearPremoveHighlights();
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                           UserMoveEvent(premoveFromX, premoveFromY,
3695                                         premoveToX, premoveToY,
3696                                         premovePromoChar);
3697                       }
3698                     }
3699
3700                     /* Usually suppress following prompt */
3701                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3702                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3703                         if (looking_at(buf, &i, "*% ")) {
3704                             savingComment = FALSE;
3705                             suppressKibitz = 0;
3706                         }
3707                     }
3708                     next_out = i;
3709                 } else if (started == STARTED_HOLDINGS) {
3710                     int gamenum;
3711                     char new_piece[MSG_SIZ];
3712                     started = STARTED_NONE;
3713                     parse[parse_pos] = NULLCHAR;
3714                     if (appData.debugMode)
3715                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3716                                                         parse, currentMove);
3717                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3718                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3719                         if (gameInfo.variant == VariantNormal) {
3720                           /* [HGM] We seem to switch variant during a game!
3721                            * Presumably no holdings were displayed, so we have
3722                            * to move the position two files to the right to
3723                            * create room for them!
3724                            */
3725                           VariantClass newVariant;
3726                           switch(gameInfo.boardWidth) { // base guess on board width
3727                                 case 9:  newVariant = VariantShogi; break;
3728                                 case 10: newVariant = VariantGreat; break;
3729                                 default: newVariant = VariantCrazyhouse; break;
3730                           }
3731                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3732                           /* Get a move list just to see the header, which
3733                              will tell us whether this is really bug or zh */
3734                           if (ics_getting_history == H_FALSE) {
3735                             ics_getting_history = H_REQUESTED;
3736                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3737                             SendToICS(str);
3738                           }
3739                         }
3740                         new_piece[0] = NULLCHAR;
3741                         sscanf(parse, "game %d white [%s black [%s <- %s",
3742                                &gamenum, white_holding, black_holding,
3743                                new_piece);
3744                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3745                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3746                         /* [HGM] copy holdings to board holdings area */
3747                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3748                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3749                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3750 #if ZIPPY
3751                         if (appData.zippyPlay && first.initDone) {
3752                             ZippyHoldings(white_holding, black_holding,
3753                                           new_piece);
3754                         }
3755 #endif /*ZIPPY*/
3756                         if (tinyLayout || smallLayout) {
3757                             char wh[16], bh[16];
3758                             PackHolding(wh, white_holding);
3759                             PackHolding(bh, black_holding);
3760                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3761                                     gameInfo.white, gameInfo.black);
3762                         } else {
3763                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3764                                     gameInfo.white, white_holding,
3765                                     gameInfo.black, black_holding);
3766                         }
3767                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3768                         DrawPosition(FALSE, boards[currentMove]);
3769                         DisplayTitle(str);
3770                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3771                         sscanf(parse, "game %d white [%s black [%s <- %s",
3772                                &gamenum, white_holding, black_holding,
3773                                new_piece);
3774                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3775                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3776                         /* [HGM] copy holdings to partner-board holdings area */
3777                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3778                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3779                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3780                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3781                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3782                       }
3783                     }
3784                     /* Suppress following prompt */
3785                     if (looking_at(buf, &i, "*% ")) {
3786                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3787                         savingComment = FALSE;
3788                         suppressKibitz = 0;
3789                     }
3790                     next_out = i;
3791                 }
3792                 continue;
3793             }
3794
3795             i++;                /* skip unparsed character and loop back */
3796         }
3797
3798         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3799 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3800 //          SendToPlayer(&buf[next_out], i - next_out);
3801             started != STARTED_HOLDINGS && leftover_start > next_out) {
3802             SendToPlayer(&buf[next_out], leftover_start - next_out);
3803             next_out = i;
3804         }
3805
3806         leftover_len = buf_len - leftover_start;
3807         /* if buffer ends with something we couldn't parse,
3808            reparse it after appending the next read */
3809
3810     } else if (count == 0) {
3811         RemoveInputSource(isr);
3812         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3813     } else {
3814         DisplayFatalError(_("Error reading from ICS"), error, 1);
3815     }
3816 }
3817
3818
3819 /* Board style 12 looks like this:
3820
3821    <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
3822
3823  * The "<12> " is stripped before it gets to this routine.  The two
3824  * trailing 0's (flip state and clock ticking) are later addition, and
3825  * some chess servers may not have them, or may have only the first.
3826  * Additional trailing fields may be added in the future.
3827  */
3828
3829 #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"
3830
3831 #define RELATION_OBSERVING_PLAYED    0
3832 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3833 #define RELATION_PLAYING_MYMOVE      1
3834 #define RELATION_PLAYING_NOTMYMOVE  -1
3835 #define RELATION_EXAMINING           2
3836 #define RELATION_ISOLATED_BOARD     -3
3837 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3838
3839 void
3840 ParseBoard12(string)
3841      char *string;
3842 {
3843     GameMode newGameMode;
3844     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3845     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3846     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3847     char to_play, board_chars[200];
3848     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3849     char black[32], white[32];
3850     Board board;
3851     int prevMove = currentMove;
3852     int ticking = 2;
3853     ChessMove moveType;
3854     int fromX, fromY, toX, toY;
3855     char promoChar;
3856     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3857     char *bookHit = NULL; // [HGM] book
3858     Boolean weird = FALSE, reqFlag = FALSE;
3859
3860     fromX = fromY = toX = toY = -1;
3861
3862     newGame = FALSE;
3863
3864     if (appData.debugMode)
3865       fprintf(debugFP, _("Parsing board: %s\n"), string);
3866
3867     move_str[0] = NULLCHAR;
3868     elapsed_time[0] = NULLCHAR;
3869     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3870         int  i = 0, j;
3871         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3872             if(string[i] == ' ') { ranks++; files = 0; }
3873             else files++;
3874             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3875             i++;
3876         }
3877         for(j = 0; j <i; j++) board_chars[j] = string[j];
3878         board_chars[i] = '\0';
3879         string += i + 1;
3880     }
3881     n = sscanf(string, PATTERN, &to_play, &double_push,
3882                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3883                &gamenum, white, black, &relation, &basetime, &increment,
3884                &white_stren, &black_stren, &white_time, &black_time,
3885                &moveNum, str, elapsed_time, move_str, &ics_flip,
3886                &ticking);
3887
3888     if (n < 21) {
3889         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3890         DisplayError(str, 0);
3891         return;
3892     }
3893
3894     /* Convert the move number to internal form */
3895     moveNum = (moveNum - 1) * 2;
3896     if (to_play == 'B') moveNum++;
3897     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3898       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3899                         0, 1);
3900       return;
3901     }
3902
3903     switch (relation) {
3904       case RELATION_OBSERVING_PLAYED:
3905       case RELATION_OBSERVING_STATIC:
3906         if (gamenum == -1) {
3907             /* Old ICC buglet */
3908             relation = RELATION_OBSERVING_STATIC;
3909         }
3910         newGameMode = IcsObserving;
3911         break;
3912       case RELATION_PLAYING_MYMOVE:
3913       case RELATION_PLAYING_NOTMYMOVE:
3914         newGameMode =
3915           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3916             IcsPlayingWhite : IcsPlayingBlack;
3917         break;
3918       case RELATION_EXAMINING:
3919         newGameMode = IcsExamining;
3920         break;
3921       case RELATION_ISOLATED_BOARD:
3922       default:
3923         /* Just display this board.  If user was doing something else,
3924            we will forget about it until the next board comes. */
3925         newGameMode = IcsIdle;
3926         break;
3927       case RELATION_STARTING_POSITION:
3928         newGameMode = gameMode;
3929         break;
3930     }
3931
3932     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3933          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3934       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3935       char *toSqr;
3936       for (k = 0; k < ranks; k++) {
3937         for (j = 0; j < files; j++)
3938           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3939         if(gameInfo.holdingsWidth > 1) {
3940              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3941              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3942         }
3943       }
3944       CopyBoard(partnerBoard, board);
3945       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3946         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3947         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3948       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3949       if(toSqr = strchr(str, '-')) {
3950         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3951         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3952       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3953       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3954       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3955       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3956       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3957       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3958                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3959       DisplayMessage(partnerStatus, "");
3960         partnerBoardValid = TRUE;
3961       return;
3962     }
3963
3964     /* Modify behavior for initial board display on move listing
3965        of wild games.
3966        */
3967     switch (ics_getting_history) {
3968       case H_FALSE:
3969       case H_REQUESTED:
3970         break;
3971       case H_GOT_REQ_HEADER:
3972       case H_GOT_UNREQ_HEADER:
3973         /* This is the initial position of the current game */
3974         gamenum = ics_gamenum;
3975         moveNum = 0;            /* old ICS bug workaround */
3976         if (to_play == 'B') {
3977           startedFromSetupPosition = TRUE;
3978           blackPlaysFirst = TRUE;
3979           moveNum = 1;
3980           if (forwardMostMove == 0) forwardMostMove = 1;
3981           if (backwardMostMove == 0) backwardMostMove = 1;
3982           if (currentMove == 0) currentMove = 1;
3983         }
3984         newGameMode = gameMode;
3985         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3986         break;
3987       case H_GOT_UNWANTED_HEADER:
3988         /* This is an initial board that we don't want */
3989         return;
3990       case H_GETTING_MOVES:
3991         /* Should not happen */
3992         DisplayError(_("Error gathering move list: extra board"), 0);
3993         ics_getting_history = H_FALSE;
3994         return;
3995     }
3996
3997    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3998                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
3999      /* [HGM] We seem to have switched variant unexpectedly
4000       * Try to guess new variant from board size
4001       */
4002           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4003           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4004           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4005           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4006           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4007           if(!weird) newVariant = VariantNormal;
4008           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4009           /* Get a move list just to see the header, which
4010              will tell us whether this is really bug or zh */
4011           if (ics_getting_history == H_FALSE) {
4012             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4013             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4014             SendToICS(str);
4015           }
4016     }
4017
4018     /* Take action if this is the first board of a new game, or of a
4019        different game than is currently being displayed.  */
4020     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4021         relation == RELATION_ISOLATED_BOARD) {
4022
4023         /* Forget the old game and get the history (if any) of the new one */
4024         if (gameMode != BeginningOfGame) {
4025           Reset(TRUE, TRUE);
4026         }
4027         newGame = TRUE;
4028         if (appData.autoRaiseBoard) BoardToTop();
4029         prevMove = -3;
4030         if (gamenum == -1) {
4031             newGameMode = IcsIdle;
4032         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4033                    appData.getMoveList && !reqFlag) {
4034             /* Need to get game history */
4035             ics_getting_history = H_REQUESTED;
4036             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4037             SendToICS(str);
4038         }
4039
4040         /* Initially flip the board to have black on the bottom if playing
4041            black or if the ICS flip flag is set, but let the user change
4042            it with the Flip View button. */
4043         flipView = appData.autoFlipView ?
4044           (newGameMode == IcsPlayingBlack) || ics_flip :
4045           appData.flipView;
4046
4047         /* Done with values from previous mode; copy in new ones */
4048         gameMode = newGameMode;
4049         ModeHighlight();
4050         ics_gamenum = gamenum;
4051         if (gamenum == gs_gamenum) {
4052             int klen = strlen(gs_kind);
4053             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4054             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4055             gameInfo.event = StrSave(str);
4056         } else {
4057             gameInfo.event = StrSave("ICS game");
4058         }
4059         gameInfo.site = StrSave(appData.icsHost);
4060         gameInfo.date = PGNDate();
4061         gameInfo.round = StrSave("-");
4062         gameInfo.white = StrSave(white);
4063         gameInfo.black = StrSave(black);
4064         timeControl = basetime * 60 * 1000;
4065         timeControl_2 = 0;
4066         timeIncrement = increment * 1000;
4067         movesPerSession = 0;
4068         gameInfo.timeControl = TimeControlTagValue();
4069         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4070   if (appData.debugMode) {
4071     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4072     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4073     setbuf(debugFP, NULL);
4074   }
4075
4076         gameInfo.outOfBook = NULL;
4077
4078         /* Do we have the ratings? */
4079         if (strcmp(player1Name, white) == 0 &&
4080             strcmp(player2Name, black) == 0) {
4081             if (appData.debugMode)
4082               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4083                       player1Rating, player2Rating);
4084             gameInfo.whiteRating = player1Rating;
4085             gameInfo.blackRating = player2Rating;
4086         } else if (strcmp(player2Name, white) == 0 &&
4087                    strcmp(player1Name, black) == 0) {
4088             if (appData.debugMode)
4089               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4090                       player2Rating, player1Rating);
4091             gameInfo.whiteRating = player2Rating;
4092             gameInfo.blackRating = player1Rating;
4093         }
4094         player1Name[0] = player2Name[0] = NULLCHAR;
4095
4096         /* Silence shouts if requested */
4097         if (appData.quietPlay &&
4098             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4099             SendToICS(ics_prefix);
4100             SendToICS("set shout 0\n");
4101         }
4102     }
4103
4104     /* Deal with midgame name changes */
4105     if (!newGame) {
4106         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4107             if (gameInfo.white) free(gameInfo.white);
4108             gameInfo.white = StrSave(white);
4109         }
4110         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4111             if (gameInfo.black) free(gameInfo.black);
4112             gameInfo.black = StrSave(black);
4113         }
4114     }
4115
4116     /* Throw away game result if anything actually changes in examine mode */
4117     if (gameMode == IcsExamining && !newGame) {
4118         gameInfo.result = GameUnfinished;
4119         if (gameInfo.resultDetails != NULL) {
4120             free(gameInfo.resultDetails);
4121             gameInfo.resultDetails = NULL;
4122         }
4123     }
4124
4125     /* In pausing && IcsExamining mode, we ignore boards coming
4126        in if they are in a different variation than we are. */
4127     if (pauseExamInvalid) return;
4128     if (pausing && gameMode == IcsExamining) {
4129         if (moveNum <= pauseExamForwardMostMove) {
4130             pauseExamInvalid = TRUE;
4131             forwardMostMove = pauseExamForwardMostMove;
4132             return;
4133         }
4134     }
4135
4136   if (appData.debugMode) {
4137     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4138   }
4139     /* Parse the board */
4140     for (k = 0; k < ranks; k++) {
4141       for (j = 0; j < files; j++)
4142         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4143       if(gameInfo.holdingsWidth > 1) {
4144            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4145            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4146       }
4147     }
4148     CopyBoard(boards[moveNum], board);
4149     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4150     if (moveNum == 0) {
4151         startedFromSetupPosition =
4152           !CompareBoards(board, initialPosition);
4153         if(startedFromSetupPosition)
4154             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4155     }
4156
4157     /* [HGM] Set castling rights. Take the outermost Rooks,
4158        to make it also work for FRC opening positions. Note that board12
4159        is really defective for later FRC positions, as it has no way to
4160        indicate which Rook can castle if they are on the same side of King.
4161        For the initial position we grant rights to the outermost Rooks,
4162        and remember thos rights, and we then copy them on positions
4163        later in an FRC game. This means WB might not recognize castlings with
4164        Rooks that have moved back to their original position as illegal,
4165        but in ICS mode that is not its job anyway.
4166     */
4167     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4168     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4169
4170         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4171             if(board[0][i] == WhiteRook) j = i;
4172         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4173         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4174             if(board[0][i] == WhiteRook) j = i;
4175         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4176         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4177             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4178         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4179         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4180             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4181         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182
4183         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4184         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4185             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4186         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187             if(board[BOARD_HEIGHT-1][k] == bKing)
4188                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4189         if(gameInfo.variant == VariantTwoKings) {
4190             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4191             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4192             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4193         }
4194     } else { int r;
4195         r = boards[moveNum][CASTLING][0] = initialRights[0];
4196         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4197         r = boards[moveNum][CASTLING][1] = initialRights[1];
4198         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4199         r = boards[moveNum][CASTLING][3] = initialRights[3];
4200         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4201         r = boards[moveNum][CASTLING][4] = initialRights[4];
4202         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4203         /* wildcastle kludge: always assume King has rights */
4204         r = boards[moveNum][CASTLING][2] = initialRights[2];
4205         r = boards[moveNum][CASTLING][5] = initialRights[5];
4206     }
4207     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4208     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4209
4210
4211     if (ics_getting_history == H_GOT_REQ_HEADER ||
4212         ics_getting_history == H_GOT_UNREQ_HEADER) {
4213         /* This was an initial position from a move list, not
4214            the current position */
4215         return;
4216     }
4217
4218     /* Update currentMove and known move number limits */
4219     newMove = newGame || moveNum > forwardMostMove;
4220
4221     if (newGame) {
4222         forwardMostMove = backwardMostMove = currentMove = moveNum;
4223         if (gameMode == IcsExamining && moveNum == 0) {
4224           /* Workaround for ICS limitation: we are not told the wild
4225              type when starting to examine a game.  But if we ask for
4226              the move list, the move list header will tell us */
4227             ics_getting_history = H_REQUESTED;
4228             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229             SendToICS(str);
4230         }
4231     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4232                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4233 #if ZIPPY
4234         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4235         /* [HGM] applied this also to an engine that is silently watching        */
4236         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4237             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4238             gameInfo.variant == currentlyInitializedVariant) {
4239           takeback = forwardMostMove - moveNum;
4240           for (i = 0; i < takeback; i++) {
4241             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4242             SendToProgram("undo\n", &first);
4243           }
4244         }
4245 #endif
4246
4247         forwardMostMove = moveNum;
4248         if (!pausing || currentMove > forwardMostMove)
4249           currentMove = forwardMostMove;
4250     } else {
4251         /* New part of history that is not contiguous with old part */
4252         if (pausing && gameMode == IcsExamining) {
4253             pauseExamInvalid = TRUE;
4254             forwardMostMove = pauseExamForwardMostMove;
4255             return;
4256         }
4257         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4258 #if ZIPPY
4259             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4260                 // [HGM] when we will receive the move list we now request, it will be
4261                 // fed to the engine from the first move on. So if the engine is not
4262                 // in the initial position now, bring it there.
4263                 InitChessProgram(&first, 0);
4264             }
4265 #endif
4266             ics_getting_history = H_REQUESTED;
4267             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4268             SendToICS(str);
4269         }
4270         forwardMostMove = backwardMostMove = currentMove = moveNum;
4271     }
4272
4273     /* Update the clocks */
4274     if (strchr(elapsed_time, '.')) {
4275       /* Time is in ms */
4276       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4277       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4278     } else {
4279       /* Time is in seconds */
4280       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4281       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4282     }
4283
4284
4285 #if ZIPPY
4286     if (appData.zippyPlay && newGame &&
4287         gameMode != IcsObserving && gameMode != IcsIdle &&
4288         gameMode != IcsExamining)
4289       ZippyFirstBoard(moveNum, basetime, increment);
4290 #endif
4291
4292     /* Put the move on the move list, first converting
4293        to canonical algebraic form. */
4294     if (moveNum > 0) {
4295   if (appData.debugMode) {
4296     if (appData.debugMode) { int f = forwardMostMove;
4297         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4298                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4299                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4300     }
4301     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4302     fprintf(debugFP, "moveNum = %d\n", moveNum);
4303     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4304     setbuf(debugFP, NULL);
4305   }
4306         if (moveNum <= backwardMostMove) {
4307             /* We don't know what the board looked like before
4308                this move.  Punt. */
4309           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4310             strcat(parseList[moveNum - 1], " ");
4311             strcat(parseList[moveNum - 1], elapsed_time);
4312             moveList[moveNum - 1][0] = NULLCHAR;
4313         } else if (strcmp(move_str, "none") == 0) {
4314             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4315             /* Again, we don't know what the board looked like;
4316                this is really the start of the game. */
4317             parseList[moveNum - 1][0] = NULLCHAR;
4318             moveList[moveNum - 1][0] = NULLCHAR;
4319             backwardMostMove = moveNum;
4320             startedFromSetupPosition = TRUE;
4321             fromX = fromY = toX = toY = -1;
4322         } else {
4323           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4324           //                 So we parse the long-algebraic move string in stead of the SAN move
4325           int valid; char buf[MSG_SIZ], *prom;
4326
4327           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4328                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4329           // str looks something like "Q/a1-a2"; kill the slash
4330           if(str[1] == '/')
4331             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4332           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4333           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4334                 strcat(buf, prom); // long move lacks promo specification!
4335           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4336                 if(appData.debugMode)
4337                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4338                 safeStrCpy(move_str, buf, MSG_SIZ);
4339           }
4340           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4341                                 &fromX, &fromY, &toX, &toY, &promoChar)
4342                || ParseOneMove(buf, moveNum - 1, &moveType,
4343                                 &fromX, &fromY, &toX, &toY, &promoChar);
4344           // end of long SAN patch
4345           if (valid) {
4346             (void) CoordsToAlgebraic(boards[moveNum - 1],
4347                                      PosFlags(moveNum - 1),
4348                                      fromY, fromX, toY, toX, promoChar,
4349                                      parseList[moveNum-1]);
4350             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4351               case MT_NONE:
4352               case MT_STALEMATE:
4353               default:
4354                 break;
4355               case MT_CHECK:
4356                 if(gameInfo.variant != VariantShogi)
4357                     strcat(parseList[moveNum - 1], "+");
4358                 break;
4359               case MT_CHECKMATE:
4360               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4361                 strcat(parseList[moveNum - 1], "#");
4362                 break;
4363             }
4364             strcat(parseList[moveNum - 1], " ");
4365             strcat(parseList[moveNum - 1], elapsed_time);
4366             /* currentMoveString is set as a side-effect of ParseOneMove */
4367             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4368             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4369             strcat(moveList[moveNum - 1], "\n");
4370
4371             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4372               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4373                 ChessSquare old, new = boards[moveNum][k][j];
4374                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4375                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4376                   if(old == new) continue;
4377                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4378                   else if(new == WhiteWazir || new == BlackWazir) {
4379                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4380                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4381                       else boards[moveNum][k][j] = old; // preserve type of Gold
4382                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4383                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4384               }
4385           } else {
4386             /* Move from ICS was illegal!?  Punt. */
4387             if (appData.debugMode) {
4388               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4389               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4390             }
4391             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4392             strcat(parseList[moveNum - 1], " ");
4393             strcat(parseList[moveNum - 1], elapsed_time);
4394             moveList[moveNum - 1][0] = NULLCHAR;
4395             fromX = fromY = toX = toY = -1;
4396           }
4397         }
4398   if (appData.debugMode) {
4399     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4400     setbuf(debugFP, NULL);
4401   }
4402
4403 #if ZIPPY
4404         /* Send move to chess program (BEFORE animating it). */
4405         if (appData.zippyPlay && !newGame && newMove &&
4406            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4407
4408             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4409                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4410                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4411                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4412                             move_str);
4413                     DisplayError(str, 0);
4414                 } else {
4415                     if (first.sendTime) {
4416                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4417                     }
4418                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4419                     if (firstMove && !bookHit) {
4420                         firstMove = FALSE;
4421                         if (first.useColors) {
4422                           SendToProgram(gameMode == IcsPlayingWhite ?
4423                                         "white\ngo\n" :
4424                                         "black\ngo\n", &first);
4425                         } else {
4426                           SendToProgram("go\n", &first);
4427                         }
4428                         first.maybeThinking = TRUE;
4429                     }
4430                 }
4431             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4432               if (moveList[moveNum - 1][0] == NULLCHAR) {
4433                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4434                 DisplayError(str, 0);
4435               } else {
4436                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4437                 SendMoveToProgram(moveNum - 1, &first);
4438               }
4439             }
4440         }
4441 #endif
4442     }
4443
4444     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4445         /* If move comes from a remote source, animate it.  If it
4446            isn't remote, it will have already been animated. */
4447         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4448             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4449         }
4450         if (!pausing && appData.highlightLastMove) {
4451             SetHighlights(fromX, fromY, toX, toY);
4452         }
4453     }
4454
4455     /* Start the clocks */
4456     whiteFlag = blackFlag = FALSE;
4457     appData.clockMode = !(basetime == 0 && increment == 0);
4458     if (ticking == 0) {
4459       ics_clock_paused = TRUE;
4460       StopClocks();
4461     } else if (ticking == 1) {
4462       ics_clock_paused = FALSE;
4463     }
4464     if (gameMode == IcsIdle ||
4465         relation == RELATION_OBSERVING_STATIC ||
4466         relation == RELATION_EXAMINING ||
4467         ics_clock_paused)
4468       DisplayBothClocks();
4469     else
4470       StartClocks();
4471
4472     /* Display opponents and material strengths */
4473     if (gameInfo.variant != VariantBughouse &&
4474         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4475         if (tinyLayout || smallLayout) {
4476             if(gameInfo.variant == VariantNormal)
4477               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4478                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4479                     basetime, increment);
4480             else
4481               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment, (int) gameInfo.variant);
4484         } else {
4485             if(gameInfo.variant == VariantNormal)
4486               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4487                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4488                     basetime, increment);
4489             else
4490               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment, VariantName(gameInfo.variant));
4493         }
4494         DisplayTitle(str);
4495   if (appData.debugMode) {
4496     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4497   }
4498     }
4499
4500
4501     /* Display the board */
4502     if (!pausing && !appData.noGUI) {
4503
4504       if (appData.premove)
4505           if (!gotPremove ||
4506              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4507              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4508               ClearPremoveHighlights();
4509
4510       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4511         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4512       DrawPosition(j, boards[currentMove]);
4513
4514       DisplayMove(moveNum - 1);
4515       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4516             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4517               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4518         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4519       }
4520     }
4521
4522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4523 #if ZIPPY
4524     if(bookHit) { // [HGM] book: simulate book reply
4525         static char bookMove[MSG_SIZ]; // a bit generous?
4526
4527         programStats.nodes = programStats.depth = programStats.time =
4528         programStats.score = programStats.got_only_move = 0;
4529         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4530
4531         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4532         strcat(bookMove, bookHit);
4533         HandleMachineMove(bookMove, &first);
4534     }
4535 #endif
4536 }
4537
4538 void
4539 GetMoveListEvent()
4540 {
4541     char buf[MSG_SIZ];
4542     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4543         ics_getting_history = H_REQUESTED;
4544         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4545         SendToICS(buf);
4546     }
4547 }
4548
4549 void
4550 AnalysisPeriodicEvent(force)
4551      int force;
4552 {
4553     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4554          && !force) || !appData.periodicUpdates)
4555       return;
4556
4557     /* Send . command to Crafty to collect stats */
4558     SendToProgram(".\n", &first);
4559
4560     /* Don't send another until we get a response (this makes
4561        us stop sending to old Crafty's which don't understand
4562        the "." command (sending illegal cmds resets node count & time,
4563        which looks bad)) */
4564     programStats.ok_to_send = 0;
4565 }
4566
4567 void ics_update_width(new_width)
4568         int new_width;
4569 {
4570         ics_printf("set width %d\n", new_width);
4571 }
4572
4573 void
4574 SendMoveToProgram(moveNum, cps)
4575      int moveNum;
4576      ChessProgramState *cps;
4577 {
4578     char buf[MSG_SIZ];
4579
4580     if (cps->useUsermove) {
4581       SendToProgram("usermove ", cps);
4582     }
4583     if (cps->useSAN) {
4584       char *space;
4585       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4586         int len = space - parseList[moveNum];
4587         memcpy(buf, parseList[moveNum], len);
4588         buf[len++] = '\n';
4589         buf[len] = NULLCHAR;
4590       } else {
4591         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4592       }
4593       SendToProgram(buf, cps);
4594     } else {
4595       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4596         AlphaRank(moveList[moveNum], 4);
4597         SendToProgram(moveList[moveNum], cps);
4598         AlphaRank(moveList[moveNum], 4); // and back
4599       } else
4600       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4601        * the engine. It would be nice to have a better way to identify castle
4602        * moves here. */
4603       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4604                                                                          && cps->useOOCastle) {
4605         int fromX = moveList[moveNum][0] - AAA;
4606         int fromY = moveList[moveNum][1] - ONE;
4607         int toX = moveList[moveNum][2] - AAA;
4608         int toY = moveList[moveNum][3] - ONE;
4609         if((boards[moveNum][fromY][fromX] == WhiteKing
4610             && boards[moveNum][toY][toX] == WhiteRook)
4611            || (boards[moveNum][fromY][fromX] == BlackKing
4612                && boards[moveNum][toY][toX] == BlackRook)) {
4613           if(toX > fromX) SendToProgram("O-O\n", cps);
4614           else SendToProgram("O-O-O\n", cps);
4615         }
4616         else SendToProgram(moveList[moveNum], cps);
4617       }
4618       else SendToProgram(moveList[moveNum], cps);
4619       /* End of additions by Tord */
4620     }
4621
4622     /* [HGM] setting up the opening has brought engine in force mode! */
4623     /*       Send 'go' if we are in a mode where machine should play. */
4624     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4625         (gameMode == TwoMachinesPlay   ||
4626 #if ZIPPY
4627          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4628 #endif
4629          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4630         SendToProgram("go\n", cps);
4631   if (appData.debugMode) {
4632     fprintf(debugFP, "(extra)\n");
4633   }
4634     }
4635     setboardSpoiledMachineBlack = 0;
4636 }
4637
4638 void
4639 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4640      ChessMove moveType;
4641      int fromX, fromY, toX, toY;
4642      char promoChar;
4643 {
4644     char user_move[MSG_SIZ];
4645
4646     switch (moveType) {
4647       default:
4648         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4649                 (int)moveType, fromX, fromY, toX, toY);
4650         DisplayError(user_move + strlen("say "), 0);
4651         break;
4652       case WhiteKingSideCastle:
4653       case BlackKingSideCastle:
4654       case WhiteQueenSideCastleWild:
4655       case BlackQueenSideCastleWild:
4656       /* PUSH Fabien */
4657       case WhiteHSideCastleFR:
4658       case BlackHSideCastleFR:
4659       /* POP Fabien */
4660         snprintf(user_move, MSG_SIZ, "o-o\n");
4661         break;
4662       case WhiteQueenSideCastle:
4663       case BlackQueenSideCastle:
4664       case WhiteKingSideCastleWild:
4665       case BlackKingSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteASideCastleFR:
4668       case BlackASideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4671         break;
4672       case WhiteNonPromotion:
4673       case BlackNonPromotion:
4674         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4675         break;
4676       case WhitePromotion:
4677       case BlackPromotion:
4678         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4679           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4681                 PieceToChar(WhiteFerz));
4682         else if(gameInfo.variant == VariantGreat)
4683           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4684                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685                 PieceToChar(WhiteMan));
4686         else
4687           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4688                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689                 promoChar);
4690         break;
4691       case WhiteDrop:
4692       case BlackDrop:
4693       drop:
4694         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4695                  ToUpper(PieceToChar((ChessSquare) fromX)),
4696                  AAA + toX, ONE + toY);
4697         break;
4698       case IllegalMove:  /* could be a variant we don't quite understand */
4699         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4700       case NormalMove:
4701       case WhiteCapturesEnPassant:
4702       case BlackCapturesEnPassant:
4703         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4704                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4705         break;
4706     }
4707     SendToICS(user_move);
4708     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4709         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4710 }
4711
4712 void
4713 UploadGameEvent()
4714 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4715     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4716     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4717     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4718         DisplayError("You cannot do this while you are playing or observing", 0);
4719         return;
4720     }
4721     if(gameMode != IcsExamining) { // is this ever not the case?
4722         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4723
4724         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4725           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4726         } else { // on FICS we must first go to general examine mode
4727           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4728         }
4729         if(gameInfo.variant != VariantNormal) {
4730             // try figure out wild number, as xboard names are not always valid on ICS
4731             for(i=1; i<=36; i++) {
4732               snprintf(buf, MSG_SIZ, "wild/%d", i);
4733                 if(StringToVariant(buf) == gameInfo.variant) break;
4734             }
4735             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4736             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4737             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4738         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4739         SendToICS(ics_prefix);
4740         SendToICS(buf);
4741         if(startedFromSetupPosition || backwardMostMove != 0) {
4742           fen = PositionToFEN(backwardMostMove, NULL);
4743           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4744             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4745             SendToICS(buf);
4746           } else { // FICS: everything has to set by separate bsetup commands
4747             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4748             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4749             SendToICS(buf);
4750             if(!WhiteOnMove(backwardMostMove)) {
4751                 SendToICS("bsetup tomove black\n");
4752             }
4753             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4754             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4755             SendToICS(buf);
4756             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4757             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4758             SendToICS(buf);
4759             i = boards[backwardMostMove][EP_STATUS];
4760             if(i >= 0) { // set e.p.
4761               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4762                 SendToICS(buf);
4763             }
4764             bsetup++;
4765           }
4766         }
4767       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4768             SendToICS("bsetup done\n"); // switch to normal examining.
4769     }
4770     for(i = backwardMostMove; i<last; i++) {
4771         char buf[20];
4772         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4773         SendToICS(buf);
4774     }
4775     SendToICS(ics_prefix);
4776     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4777 }
4778
4779 void
4780 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4781      int rf, ff, rt, ft;
4782      char promoChar;
4783      char move[7];
4784 {
4785     if (rf == DROP_RANK) {
4786       sprintf(move, "%c@%c%c\n",
4787                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4788     } else {
4789         if (promoChar == 'x' || promoChar == NULLCHAR) {
4790           sprintf(move, "%c%c%c%c\n",
4791                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4792         } else {
4793             sprintf(move, "%c%c%c%c%c\n",
4794                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4795         }
4796     }
4797 }
4798
4799 void
4800 ProcessICSInitScript(f)
4801      FILE *f;
4802 {
4803     char buf[MSG_SIZ];
4804
4805     while (fgets(buf, MSG_SIZ, f)) {
4806         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4807     }
4808
4809     fclose(f);
4810 }
4811
4812
4813 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4814 void
4815 AlphaRank(char *move, int n)
4816 {
4817 //    char *p = move, c; int x, y;
4818
4819     if (appData.debugMode) {
4820         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4821     }
4822
4823     if(move[1]=='*' &&
4824        move[2]>='0' && move[2]<='9' &&
4825        move[3]>='a' && move[3]<='x'    ) {
4826         move[1] = '@';
4827         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4828         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4829     } else
4830     if(move[0]>='0' && move[0]<='9' &&
4831        move[1]>='a' && move[1]<='x' &&
4832        move[2]>='0' && move[2]<='9' &&
4833        move[3]>='a' && move[3]<='x'    ) {
4834         /* input move, Shogi -> normal */
4835         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4836         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
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[1]=='@' &&
4841        move[3]>='0' && move[3]<='9' &&
4842        move[2]>='a' && move[2]<='x'    ) {
4843         move[1] = '*';
4844         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4845         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4846     } else
4847     if(
4848        move[0]>='a' && move[0]<='x' &&
4849        move[3]>='0' && move[3]<='9' &&
4850        move[2]>='a' && move[2]<='x'    ) {
4851          /* output move, normal -> Shogi */
4852         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4853         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4854         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4855         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4856         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4857     }
4858     if (appData.debugMode) {
4859         fprintf(debugFP, "   out = '%s'\n", move);
4860     }
4861 }
4862
4863 char yy_textstr[8000];
4864
4865 /* Parser for moves from gnuchess, ICS, or user typein box */
4866 Boolean
4867 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4868      char *move;
4869      int moveNum;
4870      ChessMove *moveType;
4871      int *fromX, *fromY, *toX, *toY;
4872      char *promoChar;
4873 {
4874     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4875
4876     switch (*moveType) {
4877       case WhitePromotion:
4878       case BlackPromotion:
4879       case WhiteNonPromotion:
4880       case BlackNonPromotion:
4881       case NormalMove:
4882       case WhiteCapturesEnPassant:
4883       case BlackCapturesEnPassant:
4884       case WhiteKingSideCastle:
4885       case WhiteQueenSideCastle:
4886       case BlackKingSideCastle:
4887       case BlackQueenSideCastle:
4888       case WhiteKingSideCastleWild:
4889       case WhiteQueenSideCastleWild:
4890       case BlackKingSideCastleWild:
4891       case BlackQueenSideCastleWild:
4892       /* Code added by Tord: */
4893       case WhiteHSideCastleFR:
4894       case WhiteASideCastleFR:
4895       case BlackHSideCastleFR:
4896       case BlackASideCastleFR:
4897       /* End of code added by Tord */
4898       case IllegalMove:         /* bug or odd chess variant */
4899         *fromX = currentMoveString[0] - AAA;
4900         *fromY = currentMoveString[1] - ONE;
4901         *toX = currentMoveString[2] - AAA;
4902         *toY = currentMoveString[3] - ONE;
4903         *promoChar = currentMoveString[4];
4904         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4905             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4906     if (appData.debugMode) {
4907         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4908     }
4909             *fromX = *fromY = *toX = *toY = 0;
4910             return FALSE;
4911         }
4912         if (appData.testLegality) {
4913           return (*moveType != IllegalMove);
4914         } else {
4915           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4916                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4917         }
4918
4919       case WhiteDrop:
4920       case BlackDrop:
4921         *fromX = *moveType == WhiteDrop ?
4922           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4923           (int) CharToPiece(ToLower(currentMoveString[0]));
4924         *fromY = DROP_RANK;
4925         *toX = currentMoveString[2] - AAA;
4926         *toY = currentMoveString[3] - ONE;
4927         *promoChar = NULLCHAR;
4928         return TRUE;
4929
4930       case AmbiguousMove:
4931       case ImpossibleMove:
4932       case EndOfFile:
4933       case ElapsedTime:
4934       case Comment:
4935       case PGNTag:
4936       case NAG:
4937       case WhiteWins:
4938       case BlackWins:
4939       case GameIsDrawn:
4940       default:
4941     if (appData.debugMode) {
4942         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4943     }
4944         /* bug? */
4945         *fromX = *fromY = *toX = *toY = 0;
4946         *promoChar = NULLCHAR;
4947         return FALSE;
4948     }
4949 }
4950
4951
4952 void
4953 ParsePV(char *pv, Boolean storeComments)
4954 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4955   int fromX, fromY, toX, toY; char promoChar;
4956   ChessMove moveType;
4957   Boolean valid;
4958   int nr = 0;
4959
4960   endPV = forwardMostMove;
4961   do {
4962     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4963     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4964     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4965 if(appData.debugMode){
4966 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);
4967 }
4968     if(!valid && nr == 0 &&
4969        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4970         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4971         // Hande case where played move is different from leading PV move
4972         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4973         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4974         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4975         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4976           endPV += 2; // if position different, keep this
4977           moveList[endPV-1][0] = fromX + AAA;
4978           moveList[endPV-1][1] = fromY + ONE;
4979           moveList[endPV-1][2] = toX + AAA;
4980           moveList[endPV-1][3] = toY + ONE;
4981           parseList[endPV-1][0] = NULLCHAR;
4982           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4983         }
4984       }
4985     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4986     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4987     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4988     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4989         valid++; // allow comments in PV
4990         continue;
4991     }
4992     nr++;
4993     if(endPV+1 > framePtr) break; // no space, truncate
4994     if(!valid) break;
4995     endPV++;
4996     CopyBoard(boards[endPV], boards[endPV-1]);
4997     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4998     moveList[endPV-1][0] = fromX + AAA;
4999     moveList[endPV-1][1] = fromY + ONE;
5000     moveList[endPV-1][2] = toX + AAA;
5001     moveList[endPV-1][3] = toY + ONE;
5002     moveList[endPV-1][4] = promoChar;
5003     moveList[endPV-1][5] = NULLCHAR;
5004     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5005     if(storeComments)
5006         CoordsToAlgebraic(boards[endPV - 1],
5007                              PosFlags(endPV - 1),
5008                              fromY, fromX, toY, toX, promoChar,
5009                              parseList[endPV - 1]);
5010     else
5011         parseList[endPV-1][0] = NULLCHAR;
5012   } while(valid);
5013   currentMove = endPV;
5014   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5015   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5016                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5017   DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 static int lastX, lastY;
5021
5022 Boolean
5023 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5024 {
5025         int startPV;
5026         char *p;
5027
5028         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5029         lastX = x; lastY = y;
5030         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5031         startPV = index;
5032         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5033         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5034         index = startPV;
5035         do{ while(buf[index] && buf[index] != '\n') index++;
5036         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5037         buf[index] = 0;
5038         ParsePV(buf+startPV, FALSE);
5039         *start = startPV; *end = index-1;
5040         return TRUE;
5041 }
5042
5043 Boolean
5044 LoadPV(int x, int y)
5045 { // called on right mouse click to load PV
5046   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5047   lastX = x; lastY = y;
5048   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5049   return TRUE;
5050 }
5051
5052 void
5053 UnLoadPV()
5054 {
5055   if(endPV < 0) return;
5056   endPV = -1;
5057   currentMove = forwardMostMove;
5058   ClearPremoveHighlights();
5059   DrawPosition(TRUE, boards[currentMove]);
5060 }
5061
5062 void
5063 MovePV(int x, int y, int h)
5064 { // step through PV based on mouse coordinates (called on mouse move)
5065   int margin = h>>3, step = 0;
5066
5067   if(endPV < 0) return;
5068   // we must somehow check if right button is still down (might be released off board!)
5069   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5070   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5071   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5072   if(!step) return;
5073   lastX = x; lastY = y;
5074   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5075   currentMove += step;
5076   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5077   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5078                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5079   DrawPosition(FALSE, boards[currentMove]);
5080 }
5081
5082
5083 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5084 // All positions will have equal probability, but the current method will not provide a unique
5085 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5086 #define DARK 1
5087 #define LITE 2
5088 #define ANY 3
5089
5090 int squaresLeft[4];
5091 int piecesLeft[(int)BlackPawn];
5092 int seed, nrOfShuffles;
5093
5094 void GetPositionNumber()
5095 {       // sets global variable seed
5096         int i;
5097
5098         seed = appData.defaultFrcPosition;
5099         if(seed < 0) { // randomize based on time for negative FRC position numbers
5100                 for(i=0; i<50; i++) seed += random();
5101                 seed = random() ^ random() >> 8 ^ random() << 8;
5102                 if(seed<0) seed = -seed;
5103         }
5104 }
5105
5106 int put(Board board, int pieceType, int rank, int n, int shade)
5107 // put the piece on the (n-1)-th empty squares of the given shade
5108 {
5109         int i;
5110
5111         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5112                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5113                         board[rank][i] = (ChessSquare) pieceType;
5114                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5115                         squaresLeft[ANY]--;
5116                         piecesLeft[pieceType]--;
5117                         return i;
5118                 }
5119         }
5120         return -1;
5121 }
5122
5123
5124 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5125 // calculate where the next piece goes, (any empty square), and put it there
5126 {
5127         int i;
5128
5129         i = seed % squaresLeft[shade];
5130         nrOfShuffles *= squaresLeft[shade];
5131         seed /= squaresLeft[shade];
5132         put(board, pieceType, rank, i, shade);
5133 }
5134
5135 void AddTwoPieces(Board board, int pieceType, int rank)
5136 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5137 {
5138         int i, n=squaresLeft[ANY], j=n-1, k;
5139
5140         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5141         i = seed % k;  // pick one
5142         nrOfShuffles *= k;
5143         seed /= k;
5144         while(i >= j) i -= j--;
5145         j = n - 1 - j; i += j;
5146         put(board, pieceType, rank, j, ANY);
5147         put(board, pieceType, rank, i, ANY);
5148 }
5149
5150 void SetUpShuffle(Board board, int number)
5151 {
5152         int i, p, first=1;
5153
5154         GetPositionNumber(); nrOfShuffles = 1;
5155
5156         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5157         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5158         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5159
5160         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5161
5162         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5163             p = (int) board[0][i];
5164             if(p < (int) BlackPawn) piecesLeft[p] ++;
5165             board[0][i] = EmptySquare;
5166         }
5167
5168         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5169             // shuffles restricted to allow normal castling put KRR first
5170             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5171                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5172             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5173                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5174             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5175                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5176             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5177                 put(board, WhiteRook, 0, 0, ANY);
5178             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5179         }
5180
5181         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5182             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5183             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5184                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5185                 while(piecesLeft[p] >= 2) {
5186                     AddOnePiece(board, p, 0, LITE);
5187                     AddOnePiece(board, p, 0, DARK);
5188                 }
5189                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5190             }
5191
5192         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5193             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5194             // but we leave King and Rooks for last, to possibly obey FRC restriction
5195             if(p == (int)WhiteRook) continue;
5196             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5197             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5198         }
5199
5200         // now everything is placed, except perhaps King (Unicorn) and Rooks
5201
5202         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5203             // Last King gets castling rights
5204             while(piecesLeft[(int)WhiteUnicorn]) {
5205                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5206                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5207             }
5208
5209             while(piecesLeft[(int)WhiteKing]) {
5210                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5211                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5212             }
5213
5214
5215         } else {
5216             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5217             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5218         }
5219
5220         // Only Rooks can be left; simply place them all
5221         while(piecesLeft[(int)WhiteRook]) {
5222                 i = put(board, WhiteRook, 0, 0, ANY);
5223                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5224                         if(first) {
5225                                 first=0;
5226                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5227                         }
5228                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5229                 }
5230         }
5231         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5232             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5233         }
5234
5235         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5236 }
5237
5238 int SetCharTable( char *table, const char * map )
5239 /* [HGM] moved here from winboard.c because of its general usefulness */
5240 /*       Basically a safe strcpy that uses the last character as King */
5241 {
5242     int result = FALSE; int NrPieces;
5243
5244     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5245                     && NrPieces >= 12 && !(NrPieces&1)) {
5246         int i; /* [HGM] Accept even length from 12 to 34 */
5247
5248         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5249         for( i=0; i<NrPieces/2-1; i++ ) {
5250             table[i] = map[i];
5251             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5252         }
5253         table[(int) WhiteKing]  = map[NrPieces/2-1];
5254         table[(int) BlackKing]  = map[NrPieces-1];
5255
5256         result = TRUE;
5257     }
5258
5259     return result;
5260 }
5261
5262 void Prelude(Board board)
5263 {       // [HGM] superchess: random selection of exo-pieces
5264         int i, j, k; ChessSquare p;
5265         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5266
5267         GetPositionNumber(); // use FRC position number
5268
5269         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5270             SetCharTable(pieceToChar, appData.pieceToCharTable);
5271             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5272                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5273         }
5274
5275         j = seed%4;                 seed /= 4;
5276         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5277         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5278         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5279         j = seed%3 + (seed%3 >= j); seed /= 3;
5280         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5281         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5282         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5283         j = seed%3;                 seed /= 3;
5284         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5285         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5286         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5287         j = seed%2 + (seed%2 >= j); seed /= 2;
5288         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5289         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5290         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5291         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5292         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5293         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5294         put(board, exoPieces[0],    0, 0, ANY);
5295         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5296 }
5297
5298 void
5299 InitPosition(redraw)
5300      int redraw;
5301 {
5302     ChessSquare (* pieces)[BOARD_FILES];
5303     int i, j, pawnRow, overrule,
5304     oldx = gameInfo.boardWidth,
5305     oldy = gameInfo.boardHeight,
5306     oldh = gameInfo.holdingsWidth;
5307     static int oldv;
5308
5309     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5310
5311     /* [AS] Initialize pv info list [HGM] and game status */
5312     {
5313         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5314             pvInfoList[i].depth = 0;
5315             boards[i][EP_STATUS] = EP_NONE;
5316             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5317         }
5318
5319         initialRulePlies = 0; /* 50-move counter start */
5320
5321         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5322         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5323     }
5324
5325
5326     /* [HGM] logic here is completely changed. In stead of full positions */
5327     /* the initialized data only consist of the two backranks. The switch */
5328     /* selects which one we will use, which is than copied to the Board   */
5329     /* initialPosition, which for the rest is initialized by Pawns and    */
5330     /* empty squares. This initial position is then copied to boards[0],  */
5331     /* possibly after shuffling, so that it remains available.            */
5332
5333     gameInfo.holdingsWidth = 0; /* default board sizes */
5334     gameInfo.boardWidth    = 8;
5335     gameInfo.boardHeight   = 8;
5336     gameInfo.holdingsSize  = 0;
5337     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5338     for(i=0; i<BOARD_FILES-2; i++)
5339       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5340     initialPosition[EP_STATUS] = EP_NONE;
5341     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5342     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5343          SetCharTable(pieceNickName, appData.pieceNickNames);
5344     else SetCharTable(pieceNickName, "............");
5345     pieces = FIDEArray;
5346
5347     switch (gameInfo.variant) {
5348     case VariantFischeRandom:
5349       shuffleOpenings = TRUE;
5350     default:
5351       break;
5352     case VariantShatranj:
5353       pieces = ShatranjArray;
5354       nrCastlingRights = 0;
5355       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5356       break;
5357     case VariantMakruk:
5358       pieces = makrukArray;
5359       nrCastlingRights = 0;
5360       startedFromSetupPosition = TRUE;
5361       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5362       break;
5363     case VariantTwoKings:
5364       pieces = twoKingsArray;
5365       break;
5366     case VariantCapaRandom:
5367       shuffleOpenings = TRUE;
5368     case VariantCapablanca:
5369       pieces = CapablancaArray;
5370       gameInfo.boardWidth = 10;
5371       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5372       break;
5373     case VariantGothic:
5374       pieces = GothicArray;
5375       gameInfo.boardWidth = 10;
5376       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5377       break;
5378     case VariantSChess:
5379       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5380       gameInfo.holdingsSize = 7;
5381       break;
5382     case VariantJanus:
5383       pieces = JanusArray;
5384       gameInfo.boardWidth = 10;
5385       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5386       nrCastlingRights = 6;
5387         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5388         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5389         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5390         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5391         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5392         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5393       break;
5394     case VariantFalcon:
5395       pieces = FalconArray;
5396       gameInfo.boardWidth = 10;
5397       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5398       break;
5399     case VariantXiangqi:
5400       pieces = XiangqiArray;
5401       gameInfo.boardWidth  = 9;
5402       gameInfo.boardHeight = 10;
5403       nrCastlingRights = 0;
5404       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5405       break;
5406     case VariantShogi:
5407       pieces = ShogiArray;
5408       gameInfo.boardWidth  = 9;
5409       gameInfo.boardHeight = 9;
5410       gameInfo.holdingsSize = 7;
5411       nrCastlingRights = 0;
5412       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5413       break;
5414     case VariantCourier:
5415       pieces = CourierArray;
5416       gameInfo.boardWidth  = 12;
5417       nrCastlingRights = 0;
5418       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5419       break;
5420     case VariantKnightmate:
5421       pieces = KnightmateArray;
5422       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5423       break;
5424     case VariantSpartan:
5425       pieces = SpartanArray;
5426       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5427       break;
5428     case VariantFairy:
5429       pieces = fairyArray;
5430       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5431       break;
5432     case VariantGreat:
5433       pieces = GreatArray;
5434       gameInfo.boardWidth = 10;
5435       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5436       gameInfo.holdingsSize = 8;
5437       break;
5438     case VariantSuper:
5439       pieces = FIDEArray;
5440       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5441       gameInfo.holdingsSize = 8;
5442       startedFromSetupPosition = TRUE;
5443       break;
5444     case VariantCrazyhouse:
5445     case VariantBughouse:
5446       pieces = FIDEArray;
5447       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5448       gameInfo.holdingsSize = 5;
5449       break;
5450     case VariantWildCastle:
5451       pieces = FIDEArray;
5452       /* !!?shuffle with kings guaranteed to be on d or e file */
5453       shuffleOpenings = 1;
5454       break;
5455     case VariantNoCastle:
5456       pieces = FIDEArray;
5457       nrCastlingRights = 0;
5458       /* !!?unconstrained back-rank shuffle */
5459       shuffleOpenings = 1;
5460       break;
5461     }
5462
5463     overrule = 0;
5464     if(appData.NrFiles >= 0) {
5465         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5466         gameInfo.boardWidth = appData.NrFiles;
5467     }
5468     if(appData.NrRanks >= 0) {
5469         gameInfo.boardHeight = appData.NrRanks;
5470     }
5471     if(appData.holdingsSize >= 0) {
5472         i = appData.holdingsSize;
5473         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5474         gameInfo.holdingsSize = i;
5475     }
5476     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5477     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5478         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5479
5480     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5481     if(pawnRow < 1) pawnRow = 1;
5482     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5483
5484     /* User pieceToChar list overrules defaults */
5485     if(appData.pieceToCharTable != NULL)
5486         SetCharTable(pieceToChar, appData.pieceToCharTable);
5487
5488     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5489
5490         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5491             s = (ChessSquare) 0; /* account holding counts in guard band */
5492         for( i=0; i<BOARD_HEIGHT; i++ )
5493             initialPosition[i][j] = s;
5494
5495         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5496         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5497         initialPosition[pawnRow][j] = WhitePawn;
5498         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5499         if(gameInfo.variant == VariantXiangqi) {
5500             if(j&1) {
5501                 initialPosition[pawnRow][j] =
5502                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5503                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5504                    initialPosition[2][j] = WhiteCannon;
5505                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5506                 }
5507             }
5508         }
5509         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5510     }
5511     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5512
5513             j=BOARD_LEFT+1;
5514             initialPosition[1][j] = WhiteBishop;
5515             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5516             j=BOARD_RGHT-2;
5517             initialPosition[1][j] = WhiteRook;
5518             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5519     }
5520
5521     if( nrCastlingRights == -1) {
5522         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5523         /*       This sets default castling rights from none to normal corners   */
5524         /* Variants with other castling rights must set them themselves above    */
5525         nrCastlingRights = 6;
5526
5527         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5528         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5529         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5530         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5531         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5532         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5533      }
5534
5535      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5536      if(gameInfo.variant == VariantGreat) { // promotion commoners
5537         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5538         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5539         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5540         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5541      }
5542      if( gameInfo.variant == VariantSChess ) {
5543       initialPosition[1][0] = BlackMarshall;
5544       initialPosition[2][0] = BlackAngel;
5545       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5546       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5547       initialPosition[1][1] = initialPosition[2][1] = 
5548       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5549      }
5550   if (appData.debugMode) {
5551     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5552   }
5553     if(shuffleOpenings) {
5554         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5555         startedFromSetupPosition = TRUE;
5556     }
5557     if(startedFromPositionFile) {
5558       /* [HGM] loadPos: use PositionFile for every new game */
5559       CopyBoard(initialPosition, filePosition);
5560       for(i=0; i<nrCastlingRights; i++)
5561           initialRights[i] = filePosition[CASTLING][i];
5562       startedFromSetupPosition = TRUE;
5563     }
5564
5565     CopyBoard(boards[0], initialPosition);
5566
5567     if(oldx != gameInfo.boardWidth ||
5568        oldy != gameInfo.boardHeight ||
5569        oldv != gameInfo.variant ||
5570        oldh != gameInfo.holdingsWidth
5571                                          )
5572             InitDrawingSizes(-2 ,0);
5573
5574     oldv = gameInfo.variant;
5575     if (redraw)
5576       DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 SendBoard(cps, moveNum)
5581      ChessProgramState *cps;
5582      int moveNum;
5583 {
5584     char message[MSG_SIZ];
5585
5586     if (cps->useSetboard) {
5587       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5588       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5589       SendToProgram(message, cps);
5590       free(fen);
5591
5592     } else {
5593       ChessSquare *bp;
5594       int i, j;
5595       /* Kludge to set black to move, avoiding the troublesome and now
5596        * deprecated "black" command.
5597        */
5598       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5599         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5600
5601       SendToProgram("edit\n", cps);
5602       SendToProgram("#\n", cps);
5603       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5604         bp = &boards[moveNum][i][BOARD_LEFT];
5605         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5606           if ((int) *bp < (int) BlackPawn) {
5607             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5608                     AAA + j, ONE + i);
5609             if(message[0] == '+' || message[0] == '~') {
5610               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5611                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5612                         AAA + j, ONE + i);
5613             }
5614             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5615                 message[1] = BOARD_RGHT   - 1 - j + '1';
5616                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5617             }
5618             SendToProgram(message, cps);
5619           }
5620         }
5621       }
5622
5623       SendToProgram("c\n", cps);
5624       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5625         bp = &boards[moveNum][i][BOARD_LEFT];
5626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5627           if (((int) *bp != (int) EmptySquare)
5628               && ((int) *bp >= (int) BlackPawn)) {
5629             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5630                     AAA + j, ONE + i);
5631             if(message[0] == '+' || message[0] == '~') {
5632               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5633                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5634                         AAA + j, ONE + i);
5635             }
5636             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5637                 message[1] = BOARD_RGHT   - 1 - j + '1';
5638                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5639             }
5640             SendToProgram(message, cps);
5641           }
5642         }
5643       }
5644
5645       SendToProgram(".\n", cps);
5646     }
5647     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5648 }
5649
5650 static int autoQueen; // [HGM] oneclick
5651
5652 int
5653 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5654 {
5655     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5656     /* [HGM] add Shogi promotions */
5657     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5658     ChessSquare piece;
5659     ChessMove moveType;
5660     Boolean premove;
5661
5662     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5663     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5664
5665     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5666       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5667         return FALSE;
5668
5669     piece = boards[currentMove][fromY][fromX];
5670     if(gameInfo.variant == VariantShogi) {
5671         promotionZoneSize = BOARD_HEIGHT/3;
5672         highestPromotingPiece = (int)WhiteFerz;
5673     } else if(gameInfo.variant == VariantMakruk) {
5674         promotionZoneSize = 3;
5675     }
5676
5677     // Treat Lance as Pawn when it is not representing Amazon
5678     if(gameInfo.variant != VariantSuper) {
5679         if(piece == WhiteLance) piece = WhitePawn; else
5680         if(piece == BlackLance) piece = BlackPawn;
5681     }
5682
5683     // next weed out all moves that do not touch the promotion zone at all
5684     if((int)piece >= BlackPawn) {
5685         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5686              return FALSE;
5687         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5688     } else {
5689         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5690            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5691     }
5692
5693     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5694
5695     // weed out mandatory Shogi promotions
5696     if(gameInfo.variant == VariantShogi) {
5697         if(piece >= BlackPawn) {
5698             if(toY == 0 && piece == BlackPawn ||
5699                toY == 0 && piece == BlackQueen ||
5700                toY <= 1 && piece == BlackKnight) {
5701                 *promoChoice = '+';
5702                 return FALSE;
5703             }
5704         } else {
5705             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5706                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5707                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5708                 *promoChoice = '+';
5709                 return FALSE;
5710             }
5711         }
5712     }
5713
5714     // weed out obviously illegal Pawn moves
5715     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5716         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5717         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5718         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5719         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5720         // note we are not allowed to test for valid (non-)capture, due to premove
5721     }
5722
5723     // we either have a choice what to promote to, or (in Shogi) whether to promote
5724     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5725         *promoChoice = PieceToChar(BlackFerz);  // no choice
5726         return FALSE;
5727     }
5728     // no sense asking what we must promote to if it is going to explode...
5729     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5730         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5731         return FALSE;
5732     }
5733     if(autoQueen) { // predetermined
5734         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5735              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5736         else *promoChoice = PieceToChar(BlackQueen);
5737         return FALSE;
5738     }
5739
5740     // suppress promotion popup on illegal moves that are not premoves
5741     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5742               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5743     if(appData.testLegality && !premove) {
5744         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5745                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5746         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5747             return FALSE;
5748     }
5749
5750     return TRUE;
5751 }
5752
5753 int
5754 InPalace(row, column)
5755      int row, column;
5756 {   /* [HGM] for Xiangqi */
5757     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5758          column < (BOARD_WIDTH + 4)/2 &&
5759          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5760     return FALSE;
5761 }
5762
5763 int
5764 PieceForSquare (x, y)
5765      int x;
5766      int y;
5767 {
5768   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5769      return -1;
5770   else
5771      return boards[currentMove][y][x];
5772 }
5773
5774 int
5775 OKToStartUserMove(x, y)
5776      int x, y;
5777 {
5778     ChessSquare from_piece;
5779     int white_piece;
5780
5781     if (matchMode) return FALSE;
5782     if (gameMode == EditPosition) return TRUE;
5783
5784     if (x >= 0 && y >= 0)
5785       from_piece = boards[currentMove][y][x];
5786     else
5787       from_piece = EmptySquare;
5788
5789     if (from_piece == EmptySquare) return FALSE;
5790
5791     white_piece = (int)from_piece >= (int)WhitePawn &&
5792       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5793
5794     switch (gameMode) {
5795       case PlayFromGameFile:
5796       case AnalyzeFile:
5797       case TwoMachinesPlay:
5798       case EndOfGame:
5799         return FALSE;
5800
5801       case IcsObserving:
5802       case IcsIdle:
5803         return FALSE;
5804
5805       case MachinePlaysWhite:
5806       case IcsPlayingBlack:
5807         if (appData.zippyPlay) return FALSE;
5808         if (white_piece) {
5809             DisplayMoveError(_("You are playing Black"));
5810             return FALSE;
5811         }
5812         break;
5813
5814       case MachinePlaysBlack:
5815       case IcsPlayingWhite:
5816         if (appData.zippyPlay) return FALSE;
5817         if (!white_piece) {
5818             DisplayMoveError(_("You are playing White"));
5819             return FALSE;
5820         }
5821         break;
5822
5823       case EditGame:
5824         if (!white_piece && WhiteOnMove(currentMove)) {
5825             DisplayMoveError(_("It is White's turn"));
5826             return FALSE;
5827         }
5828         if (white_piece && !WhiteOnMove(currentMove)) {
5829             DisplayMoveError(_("It is Black's turn"));
5830             return FALSE;
5831         }
5832         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5833             /* Editing correspondence game history */
5834             /* Could disallow this or prompt for confirmation */
5835             cmailOldMove = -1;
5836         }
5837         break;
5838
5839       case BeginningOfGame:
5840         if (appData.icsActive) return FALSE;
5841         if (!appData.noChessProgram) {
5842             if (!white_piece) {
5843                 DisplayMoveError(_("You are playing White"));
5844                 return FALSE;
5845             }
5846         }
5847         break;
5848
5849       case Training:
5850         if (!white_piece && WhiteOnMove(currentMove)) {
5851             DisplayMoveError(_("It is White's turn"));
5852             return FALSE;
5853         }
5854         if (white_piece && !WhiteOnMove(currentMove)) {
5855             DisplayMoveError(_("It is Black's turn"));
5856             return FALSE;
5857         }
5858         break;
5859
5860       default:
5861       case IcsExamining:
5862         break;
5863     }
5864     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5865         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5866         && gameMode != AnalyzeFile && gameMode != Training) {
5867         DisplayMoveError(_("Displayed position is not current"));
5868         return FALSE;
5869     }
5870     return TRUE;
5871 }
5872
5873 Boolean
5874 OnlyMove(int *x, int *y, Boolean captures) {
5875     DisambiguateClosure cl;
5876     if (appData.zippyPlay) return FALSE;
5877     switch(gameMode) {
5878       case MachinePlaysBlack:
5879       case IcsPlayingWhite:
5880       case BeginningOfGame:
5881         if(!WhiteOnMove(currentMove)) return FALSE;
5882         break;
5883       case MachinePlaysWhite:
5884       case IcsPlayingBlack:
5885         if(WhiteOnMove(currentMove)) return FALSE;
5886         break;
5887       case EditGame:
5888         break;
5889       default:
5890         return FALSE;
5891     }
5892     cl.pieceIn = EmptySquare;
5893     cl.rfIn = *y;
5894     cl.ffIn = *x;
5895     cl.rtIn = -1;
5896     cl.ftIn = -1;
5897     cl.promoCharIn = NULLCHAR;
5898     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5899     if( cl.kind == NormalMove ||
5900         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5901         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5902         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5903       fromX = cl.ff;
5904       fromY = cl.rf;
5905       *x = cl.ft;
5906       *y = cl.rt;
5907       return TRUE;
5908     }
5909     if(cl.kind != ImpossibleMove) return FALSE;
5910     cl.pieceIn = EmptySquare;
5911     cl.rfIn = -1;
5912     cl.ffIn = -1;
5913     cl.rtIn = *y;
5914     cl.ftIn = *x;
5915     cl.promoCharIn = NULLCHAR;
5916     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5917     if( cl.kind == NormalMove ||
5918         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5919         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5920         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5921       fromX = cl.ff;
5922       fromY = cl.rf;
5923       *x = cl.ft;
5924       *y = cl.rt;
5925       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5926       return TRUE;
5927     }
5928     return FALSE;
5929 }
5930
5931 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5932 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5933 int lastLoadGameUseList = FALSE;
5934 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5935 ChessMove lastLoadGameStart = EndOfFile;
5936
5937 void
5938 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5939      int fromX, fromY, toX, toY;
5940      int promoChar;
5941 {
5942     ChessMove moveType;
5943     ChessSquare pdown, pup;
5944
5945     /* Check if the user is playing in turn.  This is complicated because we
5946        let the user "pick up" a piece before it is his turn.  So the piece he
5947        tried to pick up may have been captured by the time he puts it down!
5948        Therefore we use the color the user is supposed to be playing in this
5949        test, not the color of the piece that is currently on the starting
5950        square---except in EditGame mode, where the user is playing both
5951        sides; fortunately there the capture race can't happen.  (It can
5952        now happen in IcsExamining mode, but that's just too bad.  The user
5953        will get a somewhat confusing message in that case.)
5954        */
5955
5956     switch (gameMode) {
5957       case PlayFromGameFile:
5958       case AnalyzeFile:
5959       case TwoMachinesPlay:
5960       case EndOfGame:
5961       case IcsObserving:
5962       case IcsIdle:
5963         /* We switched into a game mode where moves are not accepted,
5964            perhaps while the mouse button was down. */
5965         return;
5966
5967       case MachinePlaysWhite:
5968         /* User is moving for Black */
5969         if (WhiteOnMove(currentMove)) {
5970             DisplayMoveError(_("It is White's turn"));
5971             return;
5972         }
5973         break;
5974
5975       case MachinePlaysBlack:
5976         /* User is moving for White */
5977         if (!WhiteOnMove(currentMove)) {
5978             DisplayMoveError(_("It is Black's turn"));
5979             return;
5980         }
5981         break;
5982
5983       case EditGame:
5984       case IcsExamining:
5985       case BeginningOfGame:
5986       case AnalyzeMode:
5987       case Training:
5988         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
5989         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5990             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5991             /* User is moving for Black */
5992             if (WhiteOnMove(currentMove)) {
5993                 DisplayMoveError(_("It is White's turn"));
5994                 return;
5995             }
5996         } else {
5997             /* User is moving for White */
5998             if (!WhiteOnMove(currentMove)) {
5999                 DisplayMoveError(_("It is Black's turn"));
6000                 return;
6001             }
6002         }
6003         break;
6004
6005       case IcsPlayingBlack:
6006         /* User is moving for Black */
6007         if (WhiteOnMove(currentMove)) {
6008             if (!appData.premove) {
6009                 DisplayMoveError(_("It is White's turn"));
6010             } else if (toX >= 0 && toY >= 0) {
6011                 premoveToX = toX;
6012                 premoveToY = toY;
6013                 premoveFromX = fromX;
6014                 premoveFromY = fromY;
6015                 premovePromoChar = promoChar;
6016                 gotPremove = 1;
6017                 if (appData.debugMode)
6018                     fprintf(debugFP, "Got premove: fromX %d,"
6019                             "fromY %d, toX %d, toY %d\n",
6020                             fromX, fromY, toX, toY);
6021             }
6022             return;
6023         }
6024         break;
6025
6026       case IcsPlayingWhite:
6027         /* User is moving for White */
6028         if (!WhiteOnMove(currentMove)) {
6029             if (!appData.premove) {
6030                 DisplayMoveError(_("It is Black's turn"));
6031             } else if (toX >= 0 && toY >= 0) {
6032                 premoveToX = toX;
6033                 premoveToY = toY;
6034                 premoveFromX = fromX;
6035                 premoveFromY = fromY;
6036                 premovePromoChar = promoChar;
6037                 gotPremove = 1;
6038                 if (appData.debugMode)
6039                     fprintf(debugFP, "Got premove: fromX %d,"
6040                             "fromY %d, toX %d, toY %d\n",
6041                             fromX, fromY, toX, toY);
6042             }
6043             return;
6044         }
6045         break;
6046
6047       default:
6048         break;
6049
6050       case EditPosition:
6051         /* EditPosition, empty square, or different color piece;
6052            click-click move is possible */
6053         if (toX == -2 || toY == -2) {
6054             boards[0][fromY][fromX] = EmptySquare;
6055             DrawPosition(FALSE, boards[currentMove]);
6056             return;
6057         } else if (toX >= 0 && toY >= 0) {
6058             boards[0][toY][toX] = boards[0][fromY][fromX];
6059             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6060                 if(boards[0][fromY][0] != EmptySquare) {
6061                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6062                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6063                 }
6064             } else
6065             if(fromX == BOARD_RGHT+1) {
6066                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6067                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6068                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6069                 }
6070             } else
6071             boards[0][fromY][fromX] = EmptySquare;
6072             DrawPosition(FALSE, boards[currentMove]);
6073             return;
6074         }
6075         return;
6076     }
6077
6078     if(toX < 0 || toY < 0) return;
6079     pdown = boards[currentMove][fromY][fromX];
6080     pup = boards[currentMove][toY][toX];
6081
6082     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6083     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6084          if( pup != EmptySquare ) return;
6085          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6086            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6087                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6088            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6089            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6090            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6091            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6092          fromY = DROP_RANK;
6093     }
6094
6095     /* [HGM] always test for legality, to get promotion info */
6096     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6097                                          fromY, fromX, toY, toX, promoChar);
6098     /* [HGM] but possibly ignore an IllegalMove result */
6099     if (appData.testLegality) {
6100         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6101             DisplayMoveError(_("Illegal move"));
6102             return;
6103         }
6104     }
6105
6106     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6107 }
6108
6109 /* Common tail of UserMoveEvent and DropMenuEvent */
6110 int
6111 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6112      ChessMove moveType;
6113      int fromX, fromY, toX, toY;
6114      /*char*/int promoChar;
6115 {
6116     char *bookHit = 0;
6117
6118     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6119         // [HGM] superchess: suppress promotions to non-available piece
6120         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6121         if(WhiteOnMove(currentMove)) {
6122             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6123         } else {
6124             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6125         }
6126     }
6127
6128     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6129        move type in caller when we know the move is a legal promotion */
6130     if(moveType == NormalMove && promoChar)
6131         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6132
6133     /* [HGM] <popupFix> The following if has been moved here from
6134        UserMoveEvent(). Because it seemed to belong here (why not allow
6135        piece drops in training games?), and because it can only be
6136        performed after it is known to what we promote. */
6137     if (gameMode == Training) {
6138       /* compare the move played on the board to the next move in the
6139        * game. If they match, display the move and the opponent's response.
6140        * If they don't match, display an error message.
6141        */
6142       int saveAnimate;
6143       Board testBoard;
6144       CopyBoard(testBoard, boards[currentMove]);
6145       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6146
6147       if (CompareBoards(testBoard, boards[currentMove+1])) {
6148         ForwardInner(currentMove+1);
6149
6150         /* Autoplay the opponent's response.
6151          * if appData.animate was TRUE when Training mode was entered,
6152          * the response will be animated.
6153          */
6154         saveAnimate = appData.animate;
6155         appData.animate = animateTraining;
6156         ForwardInner(currentMove+1);
6157         appData.animate = saveAnimate;
6158
6159         /* check for the end of the game */
6160         if (currentMove >= forwardMostMove) {
6161           gameMode = PlayFromGameFile;
6162           ModeHighlight();
6163           SetTrainingModeOff();
6164           DisplayInformation(_("End of game"));
6165         }
6166       } else {
6167         DisplayError(_("Incorrect move"), 0);
6168       }
6169       return 1;
6170     }
6171
6172   /* Ok, now we know that the move is good, so we can kill
6173      the previous line in Analysis Mode */
6174   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6175                                 && currentMove < forwardMostMove) {
6176     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6177     else forwardMostMove = currentMove;
6178   }
6179
6180   /* If we need the chess program but it's dead, restart it */
6181   ResurrectChessProgram();
6182
6183   /* A user move restarts a paused game*/
6184   if (pausing)
6185     PauseEvent();
6186
6187   thinkOutput[0] = NULLCHAR;
6188
6189   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6190
6191   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6192     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6193     return 1;
6194   }
6195
6196   if (gameMode == BeginningOfGame) {
6197     if (appData.noChessProgram) {
6198       gameMode = EditGame;
6199       SetGameInfo();
6200     } else {
6201       char buf[MSG_SIZ];
6202       gameMode = MachinePlaysBlack;
6203       StartClocks();
6204       SetGameInfo();
6205       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6206       DisplayTitle(buf);
6207       if (first.sendName) {
6208         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6209         SendToProgram(buf, &first);
6210       }
6211       StartClocks();
6212     }
6213     ModeHighlight();
6214   }
6215
6216   /* Relay move to ICS or chess engine */
6217   if (appData.icsActive) {
6218     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6219         gameMode == IcsExamining) {
6220       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6221         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6222         SendToICS("draw ");
6223         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6224       }
6225       // also send plain move, in case ICS does not understand atomic claims
6226       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6227       ics_user_moved = 1;
6228     }
6229   } else {
6230     if (first.sendTime && (gameMode == BeginningOfGame ||
6231                            gameMode == MachinePlaysWhite ||
6232                            gameMode == MachinePlaysBlack)) {
6233       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6234     }
6235     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6236          // [HGM] book: if program might be playing, let it use book
6237         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6238         first.maybeThinking = TRUE;
6239     } else SendMoveToProgram(forwardMostMove-1, &first);
6240     if (currentMove == cmailOldMove + 1) {
6241       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6242     }
6243   }
6244
6245   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6246
6247   switch (gameMode) {
6248   case EditGame:
6249     if(appData.testLegality)
6250     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6251     case MT_NONE:
6252     case MT_CHECK:
6253       break;
6254     case MT_CHECKMATE:
6255     case MT_STAINMATE:
6256       if (WhiteOnMove(currentMove)) {
6257         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6258       } else {
6259         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6260       }
6261       break;
6262     case MT_STALEMATE:
6263       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6264       break;
6265     }
6266     break;
6267
6268   case MachinePlaysBlack:
6269   case MachinePlaysWhite:
6270     /* disable certain menu options while machine is thinking */
6271     SetMachineThinkingEnables();
6272     break;
6273
6274   default:
6275     break;
6276   }
6277
6278   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6279
6280   if(bookHit) { // [HGM] book: simulate book reply
6281         static char bookMove[MSG_SIZ]; // a bit generous?
6282
6283         programStats.nodes = programStats.depth = programStats.time =
6284         programStats.score = programStats.got_only_move = 0;
6285         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6286
6287         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6288         strcat(bookMove, bookHit);
6289         HandleMachineMove(bookMove, &first);
6290   }
6291   return 1;
6292 }
6293
6294 void
6295 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6296      Board board;
6297      int flags;
6298      ChessMove kind;
6299      int rf, ff, rt, ft;
6300      VOIDSTAR closure;
6301 {
6302     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6303     Markers *m = (Markers *) closure;
6304     if(rf == fromY && ff == fromX)
6305         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6306                          || kind == WhiteCapturesEnPassant
6307                          || kind == BlackCapturesEnPassant);
6308     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6309 }
6310
6311 void
6312 MarkTargetSquares(int clear)
6313 {
6314   int x, y;
6315   if(!appData.markers || !appData.highlightDragging ||
6316      !appData.testLegality || gameMode == EditPosition) return;
6317   if(clear) {
6318     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6319   } else {
6320     int capt = 0;
6321     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6322     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6323       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6324       if(capt)
6325       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6326     }
6327   }
6328   DrawPosition(TRUE, NULL);
6329 }
6330
6331 int
6332 Explode(Board board, int fromX, int fromY, int toX, int toY)
6333 {
6334     if(gameInfo.variant == VariantAtomic &&
6335        (board[toY][toX] != EmptySquare ||                     // capture?
6336         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6337                          board[fromY][fromX] == BlackPawn   )
6338       )) {
6339         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6340         return TRUE;
6341     }
6342     return FALSE;
6343 }
6344
6345 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6346
6347 void LeftClick(ClickType clickType, int xPix, int yPix)
6348 {
6349     int x, y;
6350     Boolean saveAnimate;
6351     static int second = 0, promotionChoice = 0, dragging = 0;
6352     char promoChoice = NULLCHAR;
6353
6354     if(appData.seekGraph && appData.icsActive && loggedOn &&
6355         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6356         SeekGraphClick(clickType, xPix, yPix, 0);
6357         return;
6358     }
6359
6360     if (clickType == Press) ErrorPopDown();
6361     MarkTargetSquares(1);
6362
6363     x = EventToSquare(xPix, BOARD_WIDTH);
6364     y = EventToSquare(yPix, BOARD_HEIGHT);
6365     if (!flipView && y >= 0) {
6366         y = BOARD_HEIGHT - 1 - y;
6367     }
6368     if (flipView && x >= 0) {
6369         x = BOARD_WIDTH - 1 - x;
6370     }
6371
6372     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6373         if(clickType == Release) return; // ignore upclick of click-click destination
6374         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6375         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6376         if(gameInfo.holdingsWidth &&
6377                 (WhiteOnMove(currentMove)
6378                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6379                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6380             // click in right holdings, for determining promotion piece
6381             ChessSquare p = boards[currentMove][y][x];
6382             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6383             if(p != EmptySquare) {
6384                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6385                 fromX = fromY = -1;
6386                 return;
6387             }
6388         }
6389         DrawPosition(FALSE, boards[currentMove]);
6390         return;
6391     }
6392
6393     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6394     if(clickType == Press
6395             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6396               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6397               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6398         return;
6399
6400     autoQueen = appData.alwaysPromoteToQueen;
6401
6402     if (fromX == -1) {
6403       gatingPiece = EmptySquare;
6404       if (clickType != Press) {
6405         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6406             DragPieceEnd(xPix, yPix); dragging = 0;
6407             DrawPosition(FALSE, NULL);
6408         }
6409         return;
6410       }
6411       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6412             /* First square */
6413             if (OKToStartUserMove(x, y)) {
6414                 fromX = x;
6415                 fromY = y;
6416                 second = 0;
6417                 MarkTargetSquares(0);
6418                 DragPieceBegin(xPix, yPix); dragging = 1;
6419                 if (appData.highlightDragging) {
6420                     SetHighlights(x, y, -1, -1);
6421                 }
6422             }
6423             return;
6424         }
6425     }
6426
6427     /* fromX != -1 */
6428     if (clickType == Press && gameMode != EditPosition) {
6429         ChessSquare fromP;
6430         ChessSquare toP;
6431         int frc;
6432
6433         // ignore off-board to clicks
6434         if(y < 0 || x < 0) return;
6435
6436         /* Check if clicking again on the same color piece */
6437         fromP = boards[currentMove][fromY][fromX];
6438         toP = boards[currentMove][y][x];
6439         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6440         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6441              WhitePawn <= toP && toP <= WhiteKing &&
6442              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6443              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6444             (BlackPawn <= fromP && fromP <= BlackKing &&
6445              BlackPawn <= toP && toP <= BlackKing &&
6446              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6447              !(fromP == BlackKing && toP == BlackRook && frc))) {
6448             /* Clicked again on same color piece -- changed his mind */
6449             second = (x == fromX && y == fromY);
6450            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6451             if (appData.highlightDragging) {
6452                 SetHighlights(x, y, -1, -1);
6453             } else {
6454                 ClearHighlights();
6455             }
6456             if (OKToStartUserMove(x, y)) {
6457                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6458                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6459                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6460                  gatingPiece = boards[currentMove][fromY][fromX];
6461                 else gatingPiece = EmptySquare;
6462                 fromX = x;
6463                 fromY = y; dragging = 1;
6464                 MarkTargetSquares(0);
6465                 DragPieceBegin(xPix, yPix);
6466             }
6467            }
6468            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6469            second = FALSE; 
6470         }
6471         // ignore clicks on holdings
6472         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6473     }
6474
6475     if (clickType == Release && x == fromX && y == fromY) {
6476         DragPieceEnd(xPix, yPix); dragging = 0;
6477         if (appData.animateDragging) {
6478             /* Undo animation damage if any */
6479             DrawPosition(FALSE, NULL);
6480         }
6481         if (second) {
6482             /* Second up/down in same square; just abort move */
6483             second = 0;
6484             fromX = fromY = -1;
6485             gatingPiece = EmptySquare;
6486             ClearHighlights();
6487             gotPremove = 0;
6488             ClearPremoveHighlights();
6489         } else {
6490             /* First upclick in same square; start click-click mode */
6491             SetHighlights(x, y, -1, -1);
6492         }
6493         return;
6494     }
6495
6496     /* we now have a different from- and (possibly off-board) to-square */
6497     /* Completed move */
6498     toX = x;
6499     toY = y;
6500     saveAnimate = appData.animate;
6501     if (clickType == Press) {
6502         /* Finish clickclick move */
6503         if (appData.animate || appData.highlightLastMove) {
6504             SetHighlights(fromX, fromY, toX, toY);
6505         } else {
6506             ClearHighlights();
6507         }
6508     } else {
6509         /* Finish drag move */
6510         if (appData.highlightLastMove) {
6511             SetHighlights(fromX, fromY, toX, toY);
6512         } else {
6513             ClearHighlights();
6514         }
6515         DragPieceEnd(xPix, yPix); dragging = 0;
6516         /* Don't animate move and drag both */
6517         appData.animate = FALSE;
6518     }
6519
6520     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6521     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6522         ChessSquare piece = boards[currentMove][fromY][fromX];
6523         if(gameMode == EditPosition && piece != EmptySquare &&
6524            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6525             int n;
6526
6527             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6528                 n = PieceToNumber(piece - (int)BlackPawn);
6529                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6530                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6531                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6532             } else
6533             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6534                 n = PieceToNumber(piece);
6535                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6536                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6537                 boards[currentMove][n][BOARD_WIDTH-2]++;
6538             }
6539             boards[currentMove][fromY][fromX] = EmptySquare;
6540         }
6541         ClearHighlights();
6542         fromX = fromY = -1;
6543         DrawPosition(TRUE, boards[currentMove]);
6544         return;
6545     }
6546
6547     // off-board moves should not be highlighted
6548     if(x < 0 || y < 0) ClearHighlights();
6549
6550     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6551
6552     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6553         SetHighlights(fromX, fromY, toX, toY);
6554         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6555             // [HGM] super: promotion to captured piece selected from holdings
6556             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6557             promotionChoice = TRUE;
6558             // kludge follows to temporarily execute move on display, without promoting yet
6559             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6560             boards[currentMove][toY][toX] = p;
6561             DrawPosition(FALSE, boards[currentMove]);
6562             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6563             boards[currentMove][toY][toX] = q;
6564             DisplayMessage("Click in holdings to choose piece", "");
6565             return;
6566         }
6567         PromotionPopUp();
6568     } else {
6569         int oldMove = currentMove;
6570         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6571         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6572         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6573         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6574            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6575             DrawPosition(TRUE, boards[currentMove]);
6576         fromX = fromY = -1;
6577     }
6578     appData.animate = saveAnimate;
6579     if (appData.animate || appData.animateDragging) {
6580         /* Undo animation damage if needed */
6581         DrawPosition(FALSE, NULL);
6582     }
6583 }
6584
6585 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6586 {   // front-end-free part taken out of PieceMenuPopup
6587     int whichMenu; int xSqr, ySqr;
6588
6589     if(seekGraphUp) { // [HGM] seekgraph
6590         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6591         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6592         return -2;
6593     }
6594
6595     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6596          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6597         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6598         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6599         if(action == Press)   {
6600             originalFlip = flipView;
6601             flipView = !flipView; // temporarily flip board to see game from partners perspective
6602             DrawPosition(TRUE, partnerBoard);
6603             DisplayMessage(partnerStatus, "");
6604             partnerUp = TRUE;
6605         } else if(action == Release) {
6606             flipView = originalFlip;
6607             DrawPosition(TRUE, boards[currentMove]);
6608             partnerUp = FALSE;
6609         }
6610         return -2;
6611     }
6612
6613     xSqr = EventToSquare(x, BOARD_WIDTH);
6614     ySqr = EventToSquare(y, BOARD_HEIGHT);
6615     if (action == Release) UnLoadPV(); // [HGM] pv
6616     if (action != Press) return -2; // return code to be ignored
6617     switch (gameMode) {
6618       case IcsExamining:
6619         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6620       case EditPosition:
6621         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6622         if (xSqr < 0 || ySqr < 0) return -1;\r
6623         whichMenu = 0; // edit-position menu
6624         break;
6625       case IcsObserving:
6626         if(!appData.icsEngineAnalyze) return -1;
6627       case IcsPlayingWhite:
6628       case IcsPlayingBlack:
6629         if(!appData.zippyPlay) goto noZip;
6630       case AnalyzeMode:
6631       case AnalyzeFile:
6632       case MachinePlaysWhite:
6633       case MachinePlaysBlack:
6634       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6635         if (!appData.dropMenu) {
6636           LoadPV(x, y);
6637           return 2; // flag front-end to grab mouse events
6638         }
6639         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6640            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6641       case EditGame:
6642       noZip:
6643         if (xSqr < 0 || ySqr < 0) return -1;
6644         if (!appData.dropMenu || appData.testLegality &&
6645             gameInfo.variant != VariantBughouse &&
6646             gameInfo.variant != VariantCrazyhouse) return -1;
6647         whichMenu = 1; // drop menu
6648         break;
6649       default:
6650         return -1;
6651     }
6652
6653     if (((*fromX = xSqr) < 0) ||
6654         ((*fromY = ySqr) < 0)) {
6655         *fromX = *fromY = -1;
6656         return -1;
6657     }
6658     if (flipView)
6659       *fromX = BOARD_WIDTH - 1 - *fromX;
6660     else
6661       *fromY = BOARD_HEIGHT - 1 - *fromY;
6662
6663     return whichMenu;
6664 }
6665
6666 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6667 {
6668 //    char * hint = lastHint;
6669     FrontEndProgramStats stats;
6670
6671     stats.which = cps == &first ? 0 : 1;
6672     stats.depth = cpstats->depth;
6673     stats.nodes = cpstats->nodes;
6674     stats.score = cpstats->score;
6675     stats.time = cpstats->time;
6676     stats.pv = cpstats->movelist;
6677     stats.hint = lastHint;
6678     stats.an_move_index = 0;
6679     stats.an_move_count = 0;
6680
6681     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6682         stats.hint = cpstats->move_name;
6683         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6684         stats.an_move_count = cpstats->nr_moves;
6685     }
6686
6687     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
6688
6689     SetProgramStats( &stats );
6690 }
6691
6692 void
6693 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6694 {       // count all piece types
6695         int p, f, r;
6696         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6697         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6698         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6699                 p = board[r][f];
6700                 pCnt[p]++;
6701                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6702                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6703                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6704                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6705                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6706                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6707         }
6708 }
6709
6710 int
6711 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6712 {
6713         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6714         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6715
6716         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6717         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6718         if(myPawns == 2 && nMine == 3) // KPP
6719             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6720         if(myPawns == 1 && nMine == 2) // KP
6721             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6722         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6723             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6724         if(myPawns) return FALSE;
6725         if(pCnt[WhiteRook+side])
6726             return pCnt[BlackRook-side] ||
6727                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6728                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6729                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6730         if(pCnt[WhiteCannon+side]) {
6731             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6732             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6733         }
6734         if(pCnt[WhiteKnight+side])
6735             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6736         return FALSE;
6737 }
6738
6739 int
6740 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6741 {
6742         VariantClass v = gameInfo.variant;
6743
6744         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6745         if(v == VariantShatranj) return TRUE; // always winnable through baring
6746         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6747         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6748
6749         if(v == VariantXiangqi) {
6750                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6751
6752                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6753                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6754                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6755                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6756                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6757                 if(stale) // we have at least one last-rank P plus perhaps C
6758                     return majors // KPKX
6759                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6760                 else // KCA*E*
6761                     return pCnt[WhiteFerz+side] // KCAK
6762                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6763                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6764                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6765
6766         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6767                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6768
6769                 if(nMine == 1) return FALSE; // bare King
6770                 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
6771                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6772                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6773                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6774                 if(pCnt[WhiteKnight+side])
6775                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6776                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6777                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6778                 if(nBishops)
6779                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6780                 if(pCnt[WhiteAlfil+side])
6781                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6782                 if(pCnt[WhiteWazir+side])
6783                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6784         }
6785
6786         return TRUE;
6787 }
6788
6789 int
6790 Adjudicate(ChessProgramState *cps)
6791 {       // [HGM] some adjudications useful with buggy engines
6792         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6793         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6794         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6795         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6796         int k, count = 0; static int bare = 1;
6797         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6798         Boolean canAdjudicate = !appData.icsActive;
6799
6800         // most tests only when we understand the game, i.e. legality-checking on
6801             if( appData.testLegality )
6802             {   /* [HGM] Some more adjudications for obstinate engines */
6803                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6804                 static int moveCount = 6;
6805                 ChessMove result;
6806                 char *reason = NULL;
6807
6808                 /* Count what is on board. */
6809                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6810
6811                 /* Some material-based adjudications that have to be made before stalemate test */
6812                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6813                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6814                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6815                      if(canAdjudicate && appData.checkMates) {
6816                          if(engineOpponent)
6817                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6818                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6819                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6820                          return 1;
6821                      }
6822                 }
6823
6824                 /* Bare King in Shatranj (loses) or Losers (wins) */
6825                 if( nrW == 1 || nrB == 1) {
6826                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6827                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6828                      if(canAdjudicate && appData.checkMates) {
6829                          if(engineOpponent)
6830                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6831                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6832                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6833                          return 1;
6834                      }
6835                   } else
6836                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6837                   {    /* bare King */
6838                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6839                         if(canAdjudicate && appData.checkMates) {
6840                             /* but only adjudicate if adjudication enabled */
6841                             if(engineOpponent)
6842                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6843                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6844                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6845                             return 1;
6846                         }
6847                   }
6848                 } else bare = 1;
6849
6850
6851             // don't wait for engine to announce game end if we can judge ourselves
6852             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6853               case MT_CHECK:
6854                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6855                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6856                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6857                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6858                             checkCnt++;
6859                         if(checkCnt >= 2) {
6860                             reason = "Xboard adjudication: 3rd check";
6861                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6862                             break;
6863                         }
6864                     }
6865                 }
6866               case MT_NONE:
6867               default:
6868                 break;
6869               case MT_STALEMATE:
6870               case MT_STAINMATE:
6871                 reason = "Xboard adjudication: Stalemate";
6872                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6873                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6874                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6875                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6876                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6877                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6878                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6879                                                                         EP_CHECKMATE : EP_WINS);
6880                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6881                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6882                 }
6883                 break;
6884               case MT_CHECKMATE:
6885                 reason = "Xboard adjudication: Checkmate";
6886                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6887                 break;
6888             }
6889
6890                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6891                     case EP_STALEMATE:
6892                         result = GameIsDrawn; break;
6893                     case EP_CHECKMATE:
6894                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6895                     case EP_WINS:
6896                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6897                     default:
6898                         result = EndOfFile;
6899                 }
6900                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6901                     if(engineOpponent)
6902                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6903                     GameEnds( result, reason, GE_XBOARD );
6904                     return 1;
6905                 }
6906
6907                 /* Next absolutely insufficient mating material. */
6908                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6909                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6910                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6911
6912                      /* always flag draws, for judging claims */
6913                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6914
6915                      if(canAdjudicate && appData.materialDraws) {
6916                          /* but only adjudicate them if adjudication enabled */
6917                          if(engineOpponent) {
6918                            SendToProgram("force\n", engineOpponent); // suppress reply
6919                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6920                          }
6921                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6922                          return 1;
6923                      }
6924                 }
6925
6926                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6927                 if(gameInfo.variant == VariantXiangqi ?
6928                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6929                  : nrW + nrB == 4 &&
6930                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6931                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6932                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6933                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6934                    ) ) {
6935                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6936                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6937                           if(engineOpponent) {
6938                             SendToProgram("force\n", engineOpponent); // suppress reply
6939                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6940                           }
6941                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6942                           return 1;
6943                      }
6944                 } else moveCount = 6;
6945             }
6946         if (appData.debugMode) { int i;
6947             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6948                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6949                     appData.drawRepeats);
6950             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6951               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6952
6953         }
6954
6955         // Repetition draws and 50-move rule can be applied independently of legality testing
6956
6957                 /* Check for rep-draws */
6958                 count = 0;
6959                 for(k = forwardMostMove-2;
6960                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6961                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6962                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6963                     k-=2)
6964                 {   int rights=0;
6965                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6966                         /* compare castling rights */
6967                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6968                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6969                                 rights++; /* King lost rights, while rook still had them */
6970                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6971                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6972                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6973                                    rights++; /* but at least one rook lost them */
6974                         }
6975                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6976                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6977                                 rights++;
6978                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6979                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6980                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6981                                    rights++;
6982                         }
6983                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6984                             && appData.drawRepeats > 1) {
6985                              /* adjudicate after user-specified nr of repeats */
6986                              int result = GameIsDrawn;
6987                              char *details = "XBoard adjudication: repetition draw";
6988                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6989                                 // [HGM] xiangqi: check for forbidden perpetuals
6990                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6991                                 for(m=forwardMostMove; m>k; m-=2) {
6992                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6993                                         ourPerpetual = 0; // the current mover did not always check
6994                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6995                                         hisPerpetual = 0; // the opponent did not always check
6996                                 }
6997                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6998                                                                         ourPerpetual, hisPerpetual);
6999                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7000                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7001                                     details = "Xboard adjudication: perpetual checking";
7002                                 } else
7003                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7004                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7005                                 } else
7006                                 // Now check for perpetual chases
7007                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7008                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7009                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7010                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7011                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7012                                         details = "Xboard adjudication: perpetual chasing";
7013                                     } else
7014                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7015                                         break; // Abort repetition-checking loop.
7016                                 }
7017                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7018                              }
7019                              if(engineOpponent) {
7020                                SendToProgram("force\n", engineOpponent); // suppress reply
7021                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7022                              }
7023                              GameEnds( result, details, GE_XBOARD );
7024                              return 1;
7025                         }
7026                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7027                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7028                     }
7029                 }
7030
7031                 /* Now we test for 50-move draws. Determine ply count */
7032                 count = forwardMostMove;
7033                 /* look for last irreversble move */
7034                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7035                     count--;
7036                 /* if we hit starting position, add initial plies */
7037                 if( count == backwardMostMove )
7038                     count -= initialRulePlies;
7039                 count = forwardMostMove - count;
7040                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7041                         // adjust reversible move counter for checks in Xiangqi
7042                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7043                         if(i < backwardMostMove) i = backwardMostMove;
7044                         while(i <= forwardMostMove) {
7045                                 lastCheck = inCheck; // check evasion does not count
7046                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7047                                 if(inCheck || lastCheck) count--; // check does not count
7048                                 i++;
7049                         }
7050                 }
7051                 if( count >= 100)
7052                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7053                          /* this is used to judge if draw claims are legal */
7054                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7055                          if(engineOpponent) {
7056                            SendToProgram("force\n", engineOpponent); // suppress reply
7057                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7058                          }
7059                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7060                          return 1;
7061                 }
7062
7063                 /* if draw offer is pending, treat it as a draw claim
7064                  * when draw condition present, to allow engines a way to
7065                  * claim draws before making their move to avoid a race
7066                  * condition occurring after their move
7067                  */
7068                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7069                          char *p = NULL;
7070                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7071                              p = "Draw claim: 50-move rule";
7072                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7073                              p = "Draw claim: 3-fold repetition";
7074                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7075                              p = "Draw claim: insufficient mating material";
7076                          if( p != NULL && canAdjudicate) {
7077                              if(engineOpponent) {
7078                                SendToProgram("force\n", engineOpponent); // suppress reply
7079                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7080                              }
7081                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7082                              return 1;
7083                          }
7084                 }
7085
7086                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7087                     if(engineOpponent) {
7088                       SendToProgram("force\n", engineOpponent); // suppress reply
7089                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7090                     }
7091                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7092                     return 1;
7093                 }
7094         return 0;
7095 }
7096
7097 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7098 {   // [HGM] book: this routine intercepts moves to simulate book replies
7099     char *bookHit = NULL;
7100
7101     //first determine if the incoming move brings opponent into his book
7102     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7103         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7104     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7105     if(bookHit != NULL && !cps->bookSuspend) {
7106         // make sure opponent is not going to reply after receiving move to book position
7107         SendToProgram("force\n", cps);
7108         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7109     }
7110     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7111     // now arrange restart after book miss
7112     if(bookHit) {
7113         // after a book hit we never send 'go', and the code after the call to this routine
7114         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7115         char buf[MSG_SIZ];
7116         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7117         SendToProgram(buf, cps);
7118         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7119     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7120         SendToProgram("go\n", cps);
7121         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7122     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7123         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7124             SendToProgram("go\n", cps);
7125         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7126     }
7127     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7128 }
7129
7130 char *savedMessage;
7131 ChessProgramState *savedState;
7132 void DeferredBookMove(void)
7133 {
7134         if(savedState->lastPing != savedState->lastPong)
7135                     ScheduleDelayedEvent(DeferredBookMove, 10);
7136         else
7137         HandleMachineMove(savedMessage, savedState);
7138 }
7139
7140 void
7141 HandleMachineMove(message, cps)
7142      char *message;
7143      ChessProgramState *cps;
7144 {
7145     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7146     char realname[MSG_SIZ];
7147     int fromX, fromY, toX, toY;
7148     ChessMove moveType;
7149     char promoChar;
7150     char *p;
7151     int machineWhite;
7152     char *bookHit;
7153
7154     cps->userError = 0;
7155
7156 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7157     /*
7158      * Kludge to ignore BEL characters
7159      */
7160     while (*message == '\007') message++;
7161
7162     /*
7163      * [HGM] engine debug message: ignore lines starting with '#' character
7164      */
7165     if(cps->debug && *message == '#') return;
7166
7167     /*
7168      * Look for book output
7169      */
7170     if (cps == &first && bookRequested) {
7171         if (message[0] == '\t' || message[0] == ' ') {
7172             /* Part of the book output is here; append it */
7173             strcat(bookOutput, message);
7174             strcat(bookOutput, "  \n");
7175             return;
7176         } else if (bookOutput[0] != NULLCHAR) {
7177             /* All of book output has arrived; display it */
7178             char *p = bookOutput;
7179             while (*p != NULLCHAR) {
7180                 if (*p == '\t') *p = ' ';
7181                 p++;
7182             }
7183             DisplayInformation(bookOutput);
7184             bookRequested = FALSE;
7185             /* Fall through to parse the current output */
7186         }
7187     }
7188
7189     /*
7190      * Look for machine move.
7191      */
7192     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7193         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7194     {
7195         /* This method is only useful on engines that support ping */
7196         if (cps->lastPing != cps->lastPong) {
7197           if (gameMode == BeginningOfGame) {
7198             /* Extra move from before last new; ignore */
7199             if (appData.debugMode) {
7200                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7201             }
7202           } else {
7203             if (appData.debugMode) {
7204                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7205                         cps->which, gameMode);
7206             }
7207
7208             SendToProgram("undo\n", cps);
7209           }
7210           return;
7211         }
7212
7213         switch (gameMode) {
7214           case BeginningOfGame:
7215             /* Extra move from before last reset; ignore */
7216             if (appData.debugMode) {
7217                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7218             }
7219             return;
7220
7221           case EndOfGame:
7222           case IcsIdle:
7223           default:
7224             /* Extra move after we tried to stop.  The mode test is
7225                not a reliable way of detecting this problem, but it's
7226                the best we can do on engines that don't support ping.
7227             */
7228             if (appData.debugMode) {
7229                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7230                         cps->which, gameMode);
7231             }
7232             SendToProgram("undo\n", cps);
7233             return;
7234
7235           case MachinePlaysWhite:
7236           case IcsPlayingWhite:
7237             machineWhite = TRUE;
7238             break;
7239
7240           case MachinePlaysBlack:
7241           case IcsPlayingBlack:
7242             machineWhite = FALSE;
7243             break;
7244
7245           case TwoMachinesPlay:
7246             machineWhite = (cps->twoMachinesColor[0] == 'w');
7247             break;
7248         }
7249         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7250             if (appData.debugMode) {
7251                 fprintf(debugFP,
7252                         "Ignoring move out of turn by %s, gameMode %d"
7253                         ", forwardMost %d\n",
7254                         cps->which, gameMode, forwardMostMove);
7255             }
7256             return;
7257         }
7258
7259     if (appData.debugMode) { int f = forwardMostMove;
7260         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7261                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7262                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7263     }
7264         if(cps->alphaRank) AlphaRank(machineMove, 4);
7265         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7266                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7267             /* Machine move could not be parsed; ignore it. */
7268           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7269                     machineMove, _(cps->which));
7270             DisplayError(buf1, 0);
7271             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7272                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7273             if (gameMode == TwoMachinesPlay) {
7274               GameEnds(machineWhite ? BlackWins : WhiteWins,
7275                        buf1, GE_XBOARD);
7276             }
7277             return;
7278         }
7279
7280         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7281         /* So we have to redo legality test with true e.p. status here,  */
7282         /* to make sure an illegal e.p. capture does not slip through,   */
7283         /* to cause a forfeit on a justified illegal-move complaint      */
7284         /* of the opponent.                                              */
7285         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7286            ChessMove moveType;
7287            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7288                              fromY, fromX, toY, toX, promoChar);
7289             if (appData.debugMode) {
7290                 int i;
7291                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7292                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7293                 fprintf(debugFP, "castling rights\n");
7294             }
7295             if(moveType == IllegalMove) {
7296               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7297                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7298                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7299                            buf1, GE_XBOARD);
7300                 return;
7301            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7302            /* [HGM] Kludge to handle engines that send FRC-style castling
7303               when they shouldn't (like TSCP-Gothic) */
7304            switch(moveType) {
7305              case WhiteASideCastleFR:
7306              case BlackASideCastleFR:
7307                toX+=2;
7308                currentMoveString[2]++;
7309                break;
7310              case WhiteHSideCastleFR:
7311              case BlackHSideCastleFR:
7312                toX--;
7313                currentMoveString[2]--;
7314                break;
7315              default: ; // nothing to do, but suppresses warning of pedantic compilers
7316            }
7317         }
7318         hintRequested = FALSE;
7319         lastHint[0] = NULLCHAR;
7320         bookRequested = FALSE;
7321         /* Program may be pondering now */
7322         cps->maybeThinking = TRUE;
7323         if (cps->sendTime == 2) cps->sendTime = 1;
7324         if (cps->offeredDraw) cps->offeredDraw--;
7325
7326         /* [AS] Save move info*/
7327         pvInfoList[ forwardMostMove ].score = programStats.score;
7328         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7329         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7330
7331         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7332
7333         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7334         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7335             int count = 0;
7336
7337             while( count < adjudicateLossPlies ) {
7338                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7339
7340                 if( count & 1 ) {
7341                     score = -score; /* Flip score for winning side */
7342                 }
7343
7344                 if( score > adjudicateLossThreshold ) {
7345                     break;
7346                 }
7347
7348                 count++;
7349             }
7350
7351             if( count >= adjudicateLossPlies ) {
7352                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7353
7354                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7355                     "Xboard adjudication",
7356                     GE_XBOARD );
7357
7358                 return;
7359             }
7360         }
7361
7362         if(Adjudicate(cps)) {
7363             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7364             return; // [HGM] adjudicate: for all automatic game ends
7365         }
7366
7367 #if ZIPPY
7368         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7369             first.initDone) {
7370           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7371                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7372                 SendToICS("draw ");
7373                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7374           }
7375           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7376           ics_user_moved = 1;
7377           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7378                 char buf[3*MSG_SIZ];
7379
7380                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7381                         programStats.score / 100.,
7382                         programStats.depth,
7383                         programStats.time / 100.,
7384                         (unsigned int)programStats.nodes,
7385                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7386                         programStats.movelist);
7387                 SendToICS(buf);
7388 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7389           }
7390         }
7391 #endif
7392
7393         /* [AS] Clear stats for next move */
7394         ClearProgramStats();
7395         thinkOutput[0] = NULLCHAR;
7396         hiddenThinkOutputState = 0;
7397
7398         bookHit = NULL;
7399         if (gameMode == TwoMachinesPlay) {
7400             /* [HGM] relaying draw offers moved to after reception of move */
7401             /* and interpreting offer as claim if it brings draw condition */
7402             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7403                 SendToProgram("draw\n", cps->other);
7404             }
7405             if (cps->other->sendTime) {
7406                 SendTimeRemaining(cps->other,
7407                                   cps->other->twoMachinesColor[0] == 'w');
7408             }
7409             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7410             if (firstMove && !bookHit) {
7411                 firstMove = FALSE;
7412                 if (cps->other->useColors) {
7413                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7414                 }
7415                 SendToProgram("go\n", cps->other);
7416             }
7417             cps->other->maybeThinking = TRUE;
7418         }
7419
7420         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7421
7422         if (!pausing && appData.ringBellAfterMoves) {
7423             RingBell();
7424         }
7425
7426         /*
7427          * Reenable menu items that were disabled while
7428          * machine was thinking
7429          */
7430         if (gameMode != TwoMachinesPlay)
7431             SetUserThinkingEnables();
7432
7433         // [HGM] book: after book hit opponent has received move and is now in force mode
7434         // force the book reply into it, and then fake that it outputted this move by jumping
7435         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7436         if(bookHit) {
7437                 static char bookMove[MSG_SIZ]; // a bit generous?
7438
7439                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7440                 strcat(bookMove, bookHit);
7441                 message = bookMove;
7442                 cps = cps->other;
7443                 programStats.nodes = programStats.depth = programStats.time =
7444                 programStats.score = programStats.got_only_move = 0;
7445                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7446
7447                 if(cps->lastPing != cps->lastPong) {
7448                     savedMessage = message; // args for deferred call
7449                     savedState = cps;
7450                     ScheduleDelayedEvent(DeferredBookMove, 10);
7451                     return;
7452                 }
7453                 goto FakeBookMove;
7454         }
7455
7456         return;
7457     }
7458
7459     /* Set special modes for chess engines.  Later something general
7460      *  could be added here; for now there is just one kludge feature,
7461      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7462      *  when "xboard" is given as an interactive command.
7463      */
7464     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7465         cps->useSigint = FALSE;
7466         cps->useSigterm = FALSE;
7467     }
7468     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7469       ParseFeatures(message+8, cps);
7470       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7471     }
7472
7473     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7474       int dummy, s=6; char buf[MSG_SIZ];
7475       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7476       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7477       ParseFEN(boards[0], &dummy, message+s);
7478       DrawPosition(TRUE, boards[0]);
7479       startedFromSetupPosition = TRUE;
7480       return;
7481     }
7482     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7483      * want this, I was asked to put it in, and obliged.
7484      */
7485     if (!strncmp(message, "setboard ", 9)) {
7486         Board initial_position;
7487
7488         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7489
7490         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7491             DisplayError(_("Bad FEN received from engine"), 0);
7492             return ;
7493         } else {
7494            Reset(TRUE, FALSE);
7495            CopyBoard(boards[0], initial_position);
7496            initialRulePlies = FENrulePlies;
7497            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7498            else gameMode = MachinePlaysBlack;
7499            DrawPosition(FALSE, boards[currentMove]);
7500         }
7501         return;
7502     }
7503
7504     /*
7505      * Look for communication commands
7506      */
7507     if (!strncmp(message, "telluser ", 9)) {
7508         if(message[9] == '\\' && message[10] == '\\')
7509             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7510         DisplayNote(message + 9);
7511         return;
7512     }
7513     if (!strncmp(message, "tellusererror ", 14)) {
7514         cps->userError = 1;
7515         if(message[14] == '\\' && message[15] == '\\')
7516             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7517         DisplayError(message + 14, 0);
7518         return;
7519     }
7520     if (!strncmp(message, "tellopponent ", 13)) {
7521       if (appData.icsActive) {
7522         if (loggedOn) {
7523           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7524           SendToICS(buf1);
7525         }
7526       } else {
7527         DisplayNote(message + 13);
7528       }
7529       return;
7530     }
7531     if (!strncmp(message, "tellothers ", 11)) {
7532       if (appData.icsActive) {
7533         if (loggedOn) {
7534           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7535           SendToICS(buf1);
7536         }
7537       }
7538       return;
7539     }
7540     if (!strncmp(message, "tellall ", 8)) {
7541       if (appData.icsActive) {
7542         if (loggedOn) {
7543           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7544           SendToICS(buf1);
7545         }
7546       } else {
7547         DisplayNote(message + 8);
7548       }
7549       return;
7550     }
7551     if (strncmp(message, "warning", 7) == 0) {
7552         /* Undocumented feature, use tellusererror in new code */
7553         DisplayError(message, 0);
7554         return;
7555     }
7556     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7557         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7558         strcat(realname, " query");
7559         AskQuestion(realname, buf2, buf1, cps->pr);
7560         return;
7561     }
7562     /* Commands from the engine directly to ICS.  We don't allow these to be
7563      *  sent until we are logged on. Crafty kibitzes have been known to
7564      *  interfere with the login process.
7565      */
7566     if (loggedOn) {
7567         if (!strncmp(message, "tellics ", 8)) {
7568             SendToICS(message + 8);
7569             SendToICS("\n");
7570             return;
7571         }
7572         if (!strncmp(message, "tellicsnoalias ", 15)) {
7573             SendToICS(ics_prefix);
7574             SendToICS(message + 15);
7575             SendToICS("\n");
7576             return;
7577         }
7578         /* The following are for backward compatibility only */
7579         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7580             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7581             SendToICS(ics_prefix);
7582             SendToICS(message);
7583             SendToICS("\n");
7584             return;
7585         }
7586     }
7587     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7588         return;
7589     }
7590     /*
7591      * If the move is illegal, cancel it and redraw the board.
7592      * Also deal with other error cases.  Matching is rather loose
7593      * here to accommodate engines written before the spec.
7594      */
7595     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7596         strncmp(message, "Error", 5) == 0) {
7597         if (StrStr(message, "name") ||
7598             StrStr(message, "rating") || StrStr(message, "?") ||
7599             StrStr(message, "result") || StrStr(message, "board") ||
7600             StrStr(message, "bk") || StrStr(message, "computer") ||
7601             StrStr(message, "variant") || StrStr(message, "hint") ||
7602             StrStr(message, "random") || StrStr(message, "depth") ||
7603             StrStr(message, "accepted")) {
7604             return;
7605         }
7606         if (StrStr(message, "protover")) {
7607           /* Program is responding to input, so it's apparently done
7608              initializing, and this error message indicates it is
7609              protocol version 1.  So we don't need to wait any longer
7610              for it to initialize and send feature commands. */
7611           FeatureDone(cps, 1);
7612           cps->protocolVersion = 1;
7613           return;
7614         }
7615         cps->maybeThinking = FALSE;
7616
7617         if (StrStr(message, "draw")) {
7618             /* Program doesn't have "draw" command */
7619             cps->sendDrawOffers = 0;
7620             return;
7621         }
7622         if (cps->sendTime != 1 &&
7623             (StrStr(message, "time") || StrStr(message, "otim"))) {
7624           /* Program apparently doesn't have "time" or "otim" command */
7625           cps->sendTime = 0;
7626           return;
7627         }
7628         if (StrStr(message, "analyze")) {
7629             cps->analysisSupport = FALSE;
7630             cps->analyzing = FALSE;
7631             Reset(FALSE, TRUE);
7632             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7633             DisplayError(buf2, 0);
7634             return;
7635         }
7636         if (StrStr(message, "(no matching move)st")) {
7637           /* Special kludge for GNU Chess 4 only */
7638           cps->stKludge = TRUE;
7639           SendTimeControl(cps, movesPerSession, timeControl,
7640                           timeIncrement, appData.searchDepth,
7641                           searchTime);
7642           return;
7643         }
7644         if (StrStr(message, "(no matching move)sd")) {
7645           /* Special kludge for GNU Chess 4 only */
7646           cps->sdKludge = TRUE;
7647           SendTimeControl(cps, movesPerSession, timeControl,
7648                           timeIncrement, appData.searchDepth,
7649                           searchTime);
7650           return;
7651         }
7652         if (!StrStr(message, "llegal")) {
7653             return;
7654         }
7655         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7656             gameMode == IcsIdle) return;
7657         if (forwardMostMove <= backwardMostMove) return;
7658         if (pausing) PauseEvent();
7659       if(appData.forceIllegal) {
7660             // [HGM] illegal: machine refused move; force position after move into it
7661           SendToProgram("force\n", cps);
7662           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7663                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7664                 // when black is to move, while there might be nothing on a2 or black
7665                 // might already have the move. So send the board as if white has the move.
7666                 // But first we must change the stm of the engine, as it refused the last move
7667                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7668                 if(WhiteOnMove(forwardMostMove)) {
7669                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7670                     SendBoard(cps, forwardMostMove); // kludgeless board
7671                 } else {
7672                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7673                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7674                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7675                 }
7676           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7677             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7678                  gameMode == TwoMachinesPlay)
7679               SendToProgram("go\n", cps);
7680             return;
7681       } else
7682         if (gameMode == PlayFromGameFile) {
7683             /* Stop reading this game file */
7684             gameMode = EditGame;
7685             ModeHighlight();
7686         }
7687         /* [HGM] illegal-move claim should forfeit game when Xboard */
7688         /* only passes fully legal moves                            */
7689         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7690             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7691                                 "False illegal-move claim", GE_XBOARD );
7692             return; // do not take back move we tested as valid
7693         }
7694         currentMove = forwardMostMove-1;
7695         DisplayMove(currentMove-1); /* before DisplayMoveError */
7696         SwitchClocks(forwardMostMove-1); // [HGM] race
7697         DisplayBothClocks();
7698         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7699                 parseList[currentMove], _(cps->which));
7700         DisplayMoveError(buf1);
7701         DrawPosition(FALSE, boards[currentMove]);
7702         return;
7703     }
7704     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7705         /* Program has a broken "time" command that
7706            outputs a string not ending in newline.
7707            Don't use it. */
7708         cps->sendTime = 0;
7709     }
7710
7711     /*
7712      * If chess program startup fails, exit with an error message.
7713      * Attempts to recover here are futile.
7714      */
7715     if ((StrStr(message, "unknown host") != NULL)
7716         || (StrStr(message, "No remote directory") != NULL)
7717         || (StrStr(message, "not found") != NULL)
7718         || (StrStr(message, "No such file") != NULL)
7719         || (StrStr(message, "can't alloc") != NULL)
7720         || (StrStr(message, "Permission denied") != NULL)) {
7721
7722         cps->maybeThinking = FALSE;
7723         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7724                 _(cps->which), cps->program, cps->host, message);
7725         RemoveInputSource(cps->isr);
7726         DisplayFatalError(buf1, 0, 1);
7727         return;
7728     }
7729
7730     /*
7731      * Look for hint output
7732      */
7733     if (sscanf(message, "Hint: %s", buf1) == 1) {
7734         if (cps == &first && hintRequested) {
7735             hintRequested = FALSE;
7736             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7737                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7738                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7739                                     PosFlags(forwardMostMove),
7740                                     fromY, fromX, toY, toX, promoChar, buf1);
7741                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7742                 DisplayInformation(buf2);
7743             } else {
7744                 /* Hint move could not be parsed!? */
7745               snprintf(buf2, sizeof(buf2),
7746                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7747                         buf1, _(cps->which));
7748                 DisplayError(buf2, 0);
7749             }
7750         } else {
7751           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7752         }
7753         return;
7754     }
7755
7756     /*
7757      * Ignore other messages if game is not in progress
7758      */
7759     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7760         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7761
7762     /*
7763      * look for win, lose, draw, or draw offer
7764      */
7765     if (strncmp(message, "1-0", 3) == 0) {
7766         char *p, *q, *r = "";
7767         p = strchr(message, '{');
7768         if (p) {
7769             q = strchr(p, '}');
7770             if (q) {
7771                 *q = NULLCHAR;
7772                 r = p + 1;
7773             }
7774         }
7775         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7776         return;
7777     } else if (strncmp(message, "0-1", 3) == 0) {
7778         char *p, *q, *r = "";
7779         p = strchr(message, '{');
7780         if (p) {
7781             q = strchr(p, '}');
7782             if (q) {
7783                 *q = NULLCHAR;
7784                 r = p + 1;
7785             }
7786         }
7787         /* Kludge for Arasan 4.1 bug */
7788         if (strcmp(r, "Black resigns") == 0) {
7789             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7790             return;
7791         }
7792         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7793         return;
7794     } else if (strncmp(message, "1/2", 3) == 0) {
7795         char *p, *q, *r = "";
7796         p = strchr(message, '{');
7797         if (p) {
7798             q = strchr(p, '}');
7799             if (q) {
7800                 *q = NULLCHAR;
7801                 r = p + 1;
7802             }
7803         }
7804
7805         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7806         return;
7807
7808     } else if (strncmp(message, "White resign", 12) == 0) {
7809         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7810         return;
7811     } else if (strncmp(message, "Black resign", 12) == 0) {
7812         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7813         return;
7814     } else if (strncmp(message, "White matches", 13) == 0 ||
7815                strncmp(message, "Black matches", 13) == 0   ) {
7816         /* [HGM] ignore GNUShogi noises */
7817         return;
7818     } else if (strncmp(message, "White", 5) == 0 &&
7819                message[5] != '(' &&
7820                StrStr(message, "Black") == NULL) {
7821         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7822         return;
7823     } else if (strncmp(message, "Black", 5) == 0 &&
7824                message[5] != '(') {
7825         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7826         return;
7827     } else if (strcmp(message, "resign") == 0 ||
7828                strcmp(message, "computer resigns") == 0) {
7829         switch (gameMode) {
7830           case MachinePlaysBlack:
7831           case IcsPlayingBlack:
7832             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7833             break;
7834           case MachinePlaysWhite:
7835           case IcsPlayingWhite:
7836             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7837             break;
7838           case TwoMachinesPlay:
7839             if (cps->twoMachinesColor[0] == 'w')
7840               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7841             else
7842               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7843             break;
7844           default:
7845             /* can't happen */
7846             break;
7847         }
7848         return;
7849     } else if (strncmp(message, "opponent mates", 14) == 0) {
7850         switch (gameMode) {
7851           case MachinePlaysBlack:
7852           case IcsPlayingBlack:
7853             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7854             break;
7855           case MachinePlaysWhite:
7856           case IcsPlayingWhite:
7857             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7858             break;
7859           case TwoMachinesPlay:
7860             if (cps->twoMachinesColor[0] == 'w')
7861               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7862             else
7863               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7864             break;
7865           default:
7866             /* can't happen */
7867             break;
7868         }
7869         return;
7870     } else if (strncmp(message, "computer mates", 14) == 0) {
7871         switch (gameMode) {
7872           case MachinePlaysBlack:
7873           case IcsPlayingBlack:
7874             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7875             break;
7876           case MachinePlaysWhite:
7877           case IcsPlayingWhite:
7878             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7879             break;
7880           case TwoMachinesPlay:
7881             if (cps->twoMachinesColor[0] == 'w')
7882               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7883             else
7884               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7885             break;
7886           default:
7887             /* can't happen */
7888             break;
7889         }
7890         return;
7891     } else if (strncmp(message, "checkmate", 9) == 0) {
7892         if (WhiteOnMove(forwardMostMove)) {
7893             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7894         } else {
7895             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7896         }
7897         return;
7898     } else if (strstr(message, "Draw") != NULL ||
7899                strstr(message, "game is a draw") != NULL) {
7900         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7901         return;
7902     } else if (strstr(message, "offer") != NULL &&
7903                strstr(message, "draw") != NULL) {
7904 #if ZIPPY
7905         if (appData.zippyPlay && first.initDone) {
7906             /* Relay offer to ICS */
7907             SendToICS(ics_prefix);
7908             SendToICS("draw\n");
7909         }
7910 #endif
7911         cps->offeredDraw = 2; /* valid until this engine moves twice */
7912         if (gameMode == TwoMachinesPlay) {
7913             if (cps->other->offeredDraw) {
7914                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7915             /* [HGM] in two-machine mode we delay relaying draw offer      */
7916             /* until after we also have move, to see if it is really claim */
7917             }
7918         } else if (gameMode == MachinePlaysWhite ||
7919                    gameMode == MachinePlaysBlack) {
7920           if (userOfferedDraw) {
7921             DisplayInformation(_("Machine accepts your draw offer"));
7922             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7923           } else {
7924             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7925           }
7926         }
7927     }
7928
7929
7930     /*
7931      * Look for thinking output
7932      */
7933     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7934           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7935                                 ) {
7936         int plylev, mvleft, mvtot, curscore, time;
7937         char mvname[MOVE_LEN];
7938         u64 nodes; // [DM]
7939         char plyext;
7940         int ignore = FALSE;
7941         int prefixHint = FALSE;
7942         mvname[0] = NULLCHAR;
7943
7944         switch (gameMode) {
7945           case MachinePlaysBlack:
7946           case IcsPlayingBlack:
7947             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7948             break;
7949           case MachinePlaysWhite:
7950           case IcsPlayingWhite:
7951             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7952             break;
7953           case AnalyzeMode:
7954           case AnalyzeFile:
7955             break;
7956           case IcsObserving: /* [DM] icsEngineAnalyze */
7957             if (!appData.icsEngineAnalyze) ignore = TRUE;
7958             break;
7959           case TwoMachinesPlay:
7960             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7961                 ignore = TRUE;
7962             }
7963             break;
7964           default:
7965             ignore = TRUE;
7966             break;
7967         }
7968
7969         if (!ignore) {
7970             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7971             buf1[0] = NULLCHAR;
7972             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7973                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7974
7975                 if (plyext != ' ' && plyext != '\t') {
7976                     time *= 100;
7977                 }
7978
7979                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7980                 if( cps->scoreIsAbsolute &&
7981                     ( gameMode == MachinePlaysBlack ||
7982                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7983                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7984                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7985                      !WhiteOnMove(currentMove)
7986                     ) )
7987                 {
7988                     curscore = -curscore;
7989                 }
7990
7991
7992                 tempStats.depth = plylev;
7993                 tempStats.nodes = nodes;
7994                 tempStats.time = time;
7995                 tempStats.score = curscore;
7996                 tempStats.got_only_move = 0;
7997
7998                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7999                         int ticklen;
8000
8001                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8002                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8003                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8004                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8005                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8006                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8007                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8008                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8009                 }
8010
8011                 /* Buffer overflow protection */
8012                 if (buf1[0] != NULLCHAR) {
8013                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8014                         && appData.debugMode) {
8015                         fprintf(debugFP,
8016                                 "PV is too long; using the first %u bytes.\n",
8017                                 (unsigned) sizeof(tempStats.movelist) - 1);
8018                     }
8019
8020                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8021                 } else {
8022                     sprintf(tempStats.movelist, " no PV\n");
8023                 }
8024
8025                 if (tempStats.seen_stat) {
8026                     tempStats.ok_to_send = 1;
8027                 }
8028
8029                 if (strchr(tempStats.movelist, '(') != NULL) {
8030                     tempStats.line_is_book = 1;
8031                     tempStats.nr_moves = 0;
8032                     tempStats.moves_left = 0;
8033                 } else {
8034                     tempStats.line_is_book = 0;
8035                 }
8036
8037                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8038                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8039
8040                 SendProgramStatsToFrontend( cps, &tempStats );
8041
8042                 /*
8043                     [AS] Protect the thinkOutput buffer from overflow... this
8044                     is only useful if buf1 hasn't overflowed first!
8045                 */
8046                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8047                          plylev,
8048                          (gameMode == TwoMachinesPlay ?
8049                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8050                          ((double) curscore) / 100.0,
8051                          prefixHint ? lastHint : "",
8052                          prefixHint ? " " : "" );
8053
8054                 if( buf1[0] != NULLCHAR ) {
8055                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8056
8057                     if( strlen(buf1) > max_len ) {
8058                         if( appData.debugMode) {
8059                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8060                         }
8061                         buf1[max_len+1] = '\0';
8062                     }
8063
8064                     strcat( thinkOutput, buf1 );
8065                 }
8066
8067                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8068                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8069                     DisplayMove(currentMove - 1);
8070                 }
8071                 return;
8072
8073             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8074                 /* crafty (9.25+) says "(only move) <move>"
8075                  * if there is only 1 legal move
8076                  */
8077                 sscanf(p, "(only move) %s", buf1);
8078                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8079                 sprintf(programStats.movelist, "%s (only move)", buf1);
8080                 programStats.depth = 1;
8081                 programStats.nr_moves = 1;
8082                 programStats.moves_left = 1;
8083                 programStats.nodes = 1;
8084                 programStats.time = 1;
8085                 programStats.got_only_move = 1;
8086
8087                 /* Not really, but we also use this member to
8088                    mean "line isn't going to change" (Crafty
8089                    isn't searching, so stats won't change) */
8090                 programStats.line_is_book = 1;
8091
8092                 SendProgramStatsToFrontend( cps, &programStats );
8093
8094                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8095                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8096                     DisplayMove(currentMove - 1);
8097                 }
8098                 return;
8099             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8100                               &time, &nodes, &plylev, &mvleft,
8101                               &mvtot, mvname) >= 5) {
8102                 /* The stat01: line is from Crafty (9.29+) in response
8103                    to the "." command */
8104                 programStats.seen_stat = 1;
8105                 cps->maybeThinking = TRUE;
8106
8107                 if (programStats.got_only_move || !appData.periodicUpdates)
8108                   return;
8109
8110                 programStats.depth = plylev;
8111                 programStats.time = time;
8112                 programStats.nodes = nodes;
8113                 programStats.moves_left = mvleft;
8114                 programStats.nr_moves = mvtot;
8115                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8116                 programStats.ok_to_send = 1;
8117                 programStats.movelist[0] = '\0';
8118
8119                 SendProgramStatsToFrontend( cps, &programStats );
8120
8121                 return;
8122
8123             } else if (strncmp(message,"++",2) == 0) {
8124                 /* Crafty 9.29+ outputs this */
8125                 programStats.got_fail = 2;
8126                 return;
8127
8128             } else if (strncmp(message,"--",2) == 0) {
8129                 /* Crafty 9.29+ outputs this */
8130                 programStats.got_fail = 1;
8131                 return;
8132
8133             } else if (thinkOutput[0] != NULLCHAR &&
8134                        strncmp(message, "    ", 4) == 0) {
8135                 unsigned message_len;
8136
8137                 p = message;
8138                 while (*p && *p == ' ') p++;
8139
8140                 message_len = strlen( p );
8141
8142                 /* [AS] Avoid buffer overflow */
8143                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8144                     strcat(thinkOutput, " ");
8145                     strcat(thinkOutput, p);
8146                 }
8147
8148                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8149                     strcat(programStats.movelist, " ");
8150                     strcat(programStats.movelist, p);
8151                 }
8152
8153                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8154                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8155                     DisplayMove(currentMove - 1);
8156                 }
8157                 return;
8158             }
8159         }
8160         else {
8161             buf1[0] = NULLCHAR;
8162
8163             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8164                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8165             {
8166                 ChessProgramStats cpstats;
8167
8168                 if (plyext != ' ' && plyext != '\t') {
8169                     time *= 100;
8170                 }
8171
8172                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8173                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8174                     curscore = -curscore;
8175                 }
8176
8177                 cpstats.depth = plylev;
8178                 cpstats.nodes = nodes;
8179                 cpstats.time = time;
8180                 cpstats.score = curscore;
8181                 cpstats.got_only_move = 0;
8182                 cpstats.movelist[0] = '\0';
8183
8184                 if (buf1[0] != NULLCHAR) {
8185                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8186                 }
8187
8188                 cpstats.ok_to_send = 0;
8189                 cpstats.line_is_book = 0;
8190                 cpstats.nr_moves = 0;
8191                 cpstats.moves_left = 0;
8192
8193                 SendProgramStatsToFrontend( cps, &cpstats );
8194             }
8195         }
8196     }
8197 }
8198
8199
8200 /* Parse a game score from the character string "game", and
8201    record it as the history of the current game.  The game
8202    score is NOT assumed to start from the standard position.
8203    The display is not updated in any way.
8204    */
8205 void
8206 ParseGameHistory(game)
8207      char *game;
8208 {
8209     ChessMove moveType;
8210     int fromX, fromY, toX, toY, boardIndex;
8211     char promoChar;
8212     char *p, *q;
8213     char buf[MSG_SIZ];
8214
8215     if (appData.debugMode)
8216       fprintf(debugFP, "Parsing game history: %s\n", game);
8217
8218     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8219     gameInfo.site = StrSave(appData.icsHost);
8220     gameInfo.date = PGNDate();
8221     gameInfo.round = StrSave("-");
8222
8223     /* Parse out names of players */
8224     while (*game == ' ') game++;
8225     p = buf;
8226     while (*game != ' ') *p++ = *game++;
8227     *p = NULLCHAR;
8228     gameInfo.white = StrSave(buf);
8229     while (*game == ' ') game++;
8230     p = buf;
8231     while (*game != ' ' && *game != '\n') *p++ = *game++;
8232     *p = NULLCHAR;
8233     gameInfo.black = StrSave(buf);
8234
8235     /* Parse moves */
8236     boardIndex = blackPlaysFirst ? 1 : 0;
8237     yynewstr(game);
8238     for (;;) {
8239         yyboardindex = boardIndex;
8240         moveType = (ChessMove) Myylex();
8241         switch (moveType) {
8242           case IllegalMove:             /* maybe suicide chess, etc. */
8243   if (appData.debugMode) {
8244     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8246     setbuf(debugFP, NULL);
8247   }
8248           case WhitePromotion:
8249           case BlackPromotion:
8250           case WhiteNonPromotion:
8251           case BlackNonPromotion:
8252           case NormalMove:
8253           case WhiteCapturesEnPassant:
8254           case BlackCapturesEnPassant:
8255           case WhiteKingSideCastle:
8256           case WhiteQueenSideCastle:
8257           case BlackKingSideCastle:
8258           case BlackQueenSideCastle:
8259           case WhiteKingSideCastleWild:
8260           case WhiteQueenSideCastleWild:
8261           case BlackKingSideCastleWild:
8262           case BlackQueenSideCastleWild:
8263           /* PUSH Fabien */
8264           case WhiteHSideCastleFR:
8265           case WhiteASideCastleFR:
8266           case BlackHSideCastleFR:
8267           case BlackASideCastleFR:
8268           /* POP Fabien */
8269             fromX = currentMoveString[0] - AAA;
8270             fromY = currentMoveString[1] - ONE;
8271             toX = currentMoveString[2] - AAA;
8272             toY = currentMoveString[3] - ONE;
8273             promoChar = currentMoveString[4];
8274             break;
8275           case WhiteDrop:
8276           case BlackDrop:
8277             fromX = moveType == WhiteDrop ?
8278               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8279             (int) CharToPiece(ToLower(currentMoveString[0]));
8280             fromY = DROP_RANK;
8281             toX = currentMoveString[2] - AAA;
8282             toY = currentMoveString[3] - ONE;
8283             promoChar = NULLCHAR;
8284             break;
8285           case AmbiguousMove:
8286             /* bug? */
8287             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8288   if (appData.debugMode) {
8289     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8290     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8291     setbuf(debugFP, NULL);
8292   }
8293             DisplayError(buf, 0);
8294             return;
8295           case ImpossibleMove:
8296             /* bug? */
8297             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8298   if (appData.debugMode) {
8299     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8300     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8301     setbuf(debugFP, NULL);
8302   }
8303             DisplayError(buf, 0);
8304             return;
8305           case EndOfFile:
8306             if (boardIndex < backwardMostMove) {
8307                 /* Oops, gap.  How did that happen? */
8308                 DisplayError(_("Gap in move list"), 0);
8309                 return;
8310             }
8311             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8312             if (boardIndex > forwardMostMove) {
8313                 forwardMostMove = boardIndex;
8314             }
8315             return;
8316           case ElapsedTime:
8317             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8318                 strcat(parseList[boardIndex-1], " ");
8319                 strcat(parseList[boardIndex-1], yy_text);
8320             }
8321             continue;
8322           case Comment:
8323           case PGNTag:
8324           case NAG:
8325           default:
8326             /* ignore */
8327             continue;
8328           case WhiteWins:
8329           case BlackWins:
8330           case GameIsDrawn:
8331           case GameUnfinished:
8332             if (gameMode == IcsExamining) {
8333                 if (boardIndex < backwardMostMove) {
8334                     /* Oops, gap.  How did that happen? */
8335                     return;
8336                 }
8337                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8338                 return;
8339             }
8340             gameInfo.result = moveType;
8341             p = strchr(yy_text, '{');
8342             if (p == NULL) p = strchr(yy_text, '(');
8343             if (p == NULL) {
8344                 p = yy_text;
8345                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8346             } else {
8347                 q = strchr(p, *p == '{' ? '}' : ')');
8348                 if (q != NULL) *q = NULLCHAR;
8349                 p++;
8350             }
8351             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8352             gameInfo.resultDetails = StrSave(p);
8353             continue;
8354         }
8355         if (boardIndex >= forwardMostMove &&
8356             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8357             backwardMostMove = blackPlaysFirst ? 1 : 0;
8358             return;
8359         }
8360         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8361                                  fromY, fromX, toY, toX, promoChar,
8362                                  parseList[boardIndex]);
8363         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8364         /* currentMoveString is set as a side-effect of yylex */
8365         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8366         strcat(moveList[boardIndex], "\n");
8367         boardIndex++;
8368         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8369         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8370           case MT_NONE:
8371           case MT_STALEMATE:
8372           default:
8373             break;
8374           case MT_CHECK:
8375             if(gameInfo.variant != VariantShogi)
8376                 strcat(parseList[boardIndex - 1], "+");
8377             break;
8378           case MT_CHECKMATE:
8379           case MT_STAINMATE:
8380             strcat(parseList[boardIndex - 1], "#");
8381             break;
8382         }
8383     }
8384 }
8385
8386
8387 /* Apply a move to the given board  */
8388 void
8389 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8390      int fromX, fromY, toX, toY;
8391      int promoChar;
8392      Board board;
8393 {
8394   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8395   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8396
8397     /* [HGM] compute & store e.p. status and castling rights for new position */
8398     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8399
8400       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8401       oldEP = (signed char)board[EP_STATUS];
8402       board[EP_STATUS] = EP_NONE;
8403
8404       if( board[toY][toX] != EmptySquare )
8405            board[EP_STATUS] = EP_CAPTURE;
8406
8407   if (fromY == DROP_RANK) {
8408         /* must be first */
8409         piece = board[toY][toX] = (ChessSquare) fromX;
8410   } else {
8411       int i;
8412
8413       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8414            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8415                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8416       } else
8417       if( board[fromY][fromX] == WhitePawn ) {
8418            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8419                board[EP_STATUS] = EP_PAWN_MOVE;
8420            if( toY-fromY==2) {
8421                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8422                         gameInfo.variant != VariantBerolina || toX < fromX)
8423                       board[EP_STATUS] = toX | berolina;
8424                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8425                         gameInfo.variant != VariantBerolina || toX > fromX)
8426                       board[EP_STATUS] = toX;
8427            }
8428       } else
8429       if( board[fromY][fromX] == BlackPawn ) {
8430            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8431                board[EP_STATUS] = EP_PAWN_MOVE;
8432            if( toY-fromY== -2) {
8433                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8434                         gameInfo.variant != VariantBerolina || toX < fromX)
8435                       board[EP_STATUS] = toX | berolina;
8436                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8437                         gameInfo.variant != VariantBerolina || toX > fromX)
8438                       board[EP_STATUS] = toX;
8439            }
8440        }
8441
8442        for(i=0; i<nrCastlingRights; i++) {
8443            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8444               board[CASTLING][i] == toX   && castlingRank[i] == toY
8445              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8446        }
8447
8448      if (fromX == toX && fromY == toY) return;
8449
8450      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8451      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8452      if(gameInfo.variant == VariantKnightmate)
8453          king += (int) WhiteUnicorn - (int) WhiteKing;
8454
8455     /* Code added by Tord: */
8456     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8457     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8458         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8459       board[fromY][fromX] = EmptySquare;
8460       board[toY][toX] = EmptySquare;
8461       if((toX > fromX) != (piece == WhiteRook)) {
8462         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8463       } else {
8464         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8465       }
8466     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8467                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8468       board[fromY][fromX] = EmptySquare;
8469       board[toY][toX] = EmptySquare;
8470       if((toX > fromX) != (piece == BlackRook)) {
8471         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8472       } else {
8473         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8474       }
8475     /* End of code added by Tord */
8476
8477     } else if (board[fromY][fromX] == king
8478         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8479         && toY == fromY && toX > fromX+1) {
8480         board[fromY][fromX] = EmptySquare;
8481         board[toY][toX] = king;
8482         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8483         board[fromY][BOARD_RGHT-1] = EmptySquare;
8484     } else if (board[fromY][fromX] == king
8485         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8486                && toY == fromY && toX < fromX-1) {
8487         board[fromY][fromX] = EmptySquare;
8488         board[toY][toX] = king;
8489         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8490         board[fromY][BOARD_LEFT] = EmptySquare;
8491     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8492                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8493                && toY >= BOARD_HEIGHT-promoRank
8494                ) {
8495         /* white pawn promotion */
8496         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8497         if (board[toY][toX] == EmptySquare) {
8498             board[toY][toX] = WhiteQueen;
8499         }
8500         if(gameInfo.variant==VariantBughouse ||
8501            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8502             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8503         board[fromY][fromX] = EmptySquare;
8504     } else if ((fromY == BOARD_HEIGHT-4)
8505                && (toX != fromX)
8506                && gameInfo.variant != VariantXiangqi
8507                && gameInfo.variant != VariantBerolina
8508                && (board[fromY][fromX] == WhitePawn)
8509                && (board[toY][toX] == EmptySquare)) {
8510         board[fromY][fromX] = EmptySquare;
8511         board[toY][toX] = WhitePawn;
8512         captured = board[toY - 1][toX];
8513         board[toY - 1][toX] = EmptySquare;
8514     } else if ((fromY == BOARD_HEIGHT-4)
8515                && (toX == fromX)
8516                && gameInfo.variant == VariantBerolina
8517                && (board[fromY][fromX] == WhitePawn)
8518                && (board[toY][toX] == EmptySquare)) {
8519         board[fromY][fromX] = EmptySquare;
8520         board[toY][toX] = WhitePawn;
8521         if(oldEP & EP_BEROLIN_A) {
8522                 captured = board[fromY][fromX-1];
8523                 board[fromY][fromX-1] = EmptySquare;
8524         }else{  captured = board[fromY][fromX+1];
8525                 board[fromY][fromX+1] = EmptySquare;
8526         }
8527     } else if (board[fromY][fromX] == king
8528         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8529                && toY == fromY && toX > fromX+1) {
8530         board[fromY][fromX] = EmptySquare;
8531         board[toY][toX] = king;
8532         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8533         board[fromY][BOARD_RGHT-1] = EmptySquare;
8534     } else if (board[fromY][fromX] == king
8535         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8536                && toY == fromY && toX < fromX-1) {
8537         board[fromY][fromX] = EmptySquare;
8538         board[toY][toX] = king;
8539         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8540         board[fromY][BOARD_LEFT] = EmptySquare;
8541     } else if (fromY == 7 && fromX == 3
8542                && board[fromY][fromX] == BlackKing
8543                && toY == 7 && toX == 5) {
8544         board[fromY][fromX] = EmptySquare;
8545         board[toY][toX] = BlackKing;
8546         board[fromY][7] = EmptySquare;
8547         board[toY][4] = BlackRook;
8548     } else if (fromY == 7 && fromX == 3
8549                && board[fromY][fromX] == BlackKing
8550                && toY == 7 && toX == 1) {
8551         board[fromY][fromX] = EmptySquare;
8552         board[toY][toX] = BlackKing;
8553         board[fromY][0] = EmptySquare;
8554         board[toY][2] = BlackRook;
8555     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8556                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8557                && toY < promoRank
8558                ) {
8559         /* black pawn promotion */
8560         board[toY][toX] = CharToPiece(ToLower(promoChar));
8561         if (board[toY][toX] == EmptySquare) {
8562             board[toY][toX] = BlackQueen;
8563         }
8564         if(gameInfo.variant==VariantBughouse ||
8565            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8566             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8567         board[fromY][fromX] = EmptySquare;
8568     } else if ((fromY == 3)
8569                && (toX != fromX)
8570                && gameInfo.variant != VariantXiangqi
8571                && gameInfo.variant != VariantBerolina
8572                && (board[fromY][fromX] == BlackPawn)
8573                && (board[toY][toX] == EmptySquare)) {
8574         board[fromY][fromX] = EmptySquare;
8575         board[toY][toX] = BlackPawn;
8576         captured = board[toY + 1][toX];
8577         board[toY + 1][toX] = EmptySquare;
8578     } else if ((fromY == 3)
8579                && (toX == fromX)
8580                && gameInfo.variant == VariantBerolina
8581                && (board[fromY][fromX] == BlackPawn)
8582                && (board[toY][toX] == EmptySquare)) {
8583         board[fromY][fromX] = EmptySquare;
8584         board[toY][toX] = BlackPawn;
8585         if(oldEP & EP_BEROLIN_A) {
8586                 captured = board[fromY][fromX-1];
8587                 board[fromY][fromX-1] = EmptySquare;
8588         }else{  captured = board[fromY][fromX+1];
8589                 board[fromY][fromX+1] = EmptySquare;
8590         }
8591     } else {
8592         board[toY][toX] = board[fromY][fromX];
8593         board[fromY][fromX] = EmptySquare;
8594     }
8595   }
8596
8597     if (gameInfo.holdingsWidth != 0) {
8598
8599       /* !!A lot more code needs to be written to support holdings  */
8600       /* [HGM] OK, so I have written it. Holdings are stored in the */
8601       /* penultimate board files, so they are automaticlly stored   */
8602       /* in the game history.                                       */
8603       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8604                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8605         /* Delete from holdings, by decreasing count */
8606         /* and erasing image if necessary            */
8607         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8608         if(p < (int) BlackPawn) { /* white drop */
8609              p -= (int)WhitePawn;
8610                  p = PieceToNumber((ChessSquare)p);
8611              if(p >= gameInfo.holdingsSize) p = 0;
8612              if(--board[p][BOARD_WIDTH-2] <= 0)
8613                   board[p][BOARD_WIDTH-1] = EmptySquare;
8614              if((int)board[p][BOARD_WIDTH-2] < 0)
8615                         board[p][BOARD_WIDTH-2] = 0;
8616         } else {                  /* black drop */
8617              p -= (int)BlackPawn;
8618                  p = PieceToNumber((ChessSquare)p);
8619              if(p >= gameInfo.holdingsSize) p = 0;
8620              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8621                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8622              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8623                         board[BOARD_HEIGHT-1-p][1] = 0;
8624         }
8625       }
8626       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8627           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8628         /* [HGM] holdings: Add to holdings, if holdings exist */
8629         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8630                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8631                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8632         }
8633         p = (int) captured;
8634         if (p >= (int) BlackPawn) {
8635           p -= (int)BlackPawn;
8636           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8637                   /* in Shogi restore piece to its original  first */
8638                   captured = (ChessSquare) (DEMOTED captured);
8639                   p = DEMOTED p;
8640           }
8641           p = PieceToNumber((ChessSquare)p);
8642           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8643           board[p][BOARD_WIDTH-2]++;
8644           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8645         } else {
8646           p -= (int)WhitePawn;
8647           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8648                   captured = (ChessSquare) (DEMOTED captured);
8649                   p = DEMOTED p;
8650           }
8651           p = PieceToNumber((ChessSquare)p);
8652           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8653           board[BOARD_HEIGHT-1-p][1]++;
8654           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8655         }
8656       }
8657     } else if (gameInfo.variant == VariantAtomic) {
8658       if (captured != EmptySquare) {
8659         int y, x;
8660         for (y = toY-1; y <= toY+1; y++) {
8661           for (x = toX-1; x <= toX+1; x++) {
8662             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8663                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8664               board[y][x] = EmptySquare;
8665             }
8666           }
8667         }
8668         board[toY][toX] = EmptySquare;
8669       }
8670     }
8671     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8672         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8673     } else
8674     if(promoChar == '+') {
8675         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8676         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8677     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8678         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8679     }
8680     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8681                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8682         // [HGM] superchess: take promotion piece out of holdings
8683         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8684         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8685             if(!--board[k][BOARD_WIDTH-2])
8686                 board[k][BOARD_WIDTH-1] = EmptySquare;
8687         } else {
8688             if(!--board[BOARD_HEIGHT-1-k][1])
8689                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8690         }
8691     }
8692
8693 }
8694
8695 /* Updates forwardMostMove */
8696 void
8697 MakeMove(fromX, fromY, toX, toY, promoChar)
8698      int fromX, fromY, toX, toY;
8699      int promoChar;
8700 {
8701 //    forwardMostMove++; // [HGM] bare: moved downstream
8702
8703     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8704         int timeLeft; static int lastLoadFlag=0; int king, piece;
8705         piece = boards[forwardMostMove][fromY][fromX];
8706         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8707         if(gameInfo.variant == VariantKnightmate)
8708             king += (int) WhiteUnicorn - (int) WhiteKing;
8709         if(forwardMostMove == 0) {
8710             if(blackPlaysFirst)
8711                 fprintf(serverMoves, "%s;", second.tidy);
8712             fprintf(serverMoves, "%s;", first.tidy);
8713             if(!blackPlaysFirst)
8714                 fprintf(serverMoves, "%s;", second.tidy);
8715         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8716         lastLoadFlag = loadFlag;
8717         // print base move
8718         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8719         // print castling suffix
8720         if( toY == fromY && piece == king ) {
8721             if(toX-fromX > 1)
8722                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8723             if(fromX-toX >1)
8724                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8725         }
8726         // e.p. suffix
8727         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8728              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8729              boards[forwardMostMove][toY][toX] == EmptySquare
8730              && fromX != toX && fromY != toY)
8731                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8732         // promotion suffix
8733         if(promoChar != NULLCHAR)
8734                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8735         if(!loadFlag) {
8736             fprintf(serverMoves, "/%d/%d",
8737                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8738             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8739             else                      timeLeft = blackTimeRemaining/1000;
8740             fprintf(serverMoves, "/%d", timeLeft);
8741         }
8742         fflush(serverMoves);
8743     }
8744
8745     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8746       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8747                         0, 1);
8748       return;
8749     }
8750     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8751     if (commentList[forwardMostMove+1] != NULL) {
8752         free(commentList[forwardMostMove+1]);
8753         commentList[forwardMostMove+1] = NULL;
8754     }
8755     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8756     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8757     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8758     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8759     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8760     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8761     gameInfo.result = GameUnfinished;
8762     if (gameInfo.resultDetails != NULL) {
8763         free(gameInfo.resultDetails);
8764         gameInfo.resultDetails = NULL;
8765     }
8766     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8767                               moveList[forwardMostMove - 1]);
8768     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8769                              PosFlags(forwardMostMove - 1),
8770                              fromY, fromX, toY, toX, promoChar,
8771                              parseList[forwardMostMove - 1]);
8772     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8773       case MT_NONE:
8774       case MT_STALEMATE:
8775       default:
8776         break;
8777       case MT_CHECK:
8778         if(gameInfo.variant != VariantShogi)
8779             strcat(parseList[forwardMostMove - 1], "+");
8780         break;
8781       case MT_CHECKMATE:
8782       case MT_STAINMATE:
8783         strcat(parseList[forwardMostMove - 1], "#");
8784         break;
8785     }
8786     if (appData.debugMode) {
8787         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8788     }
8789
8790 }
8791
8792 /* Updates currentMove if not pausing */
8793 void
8794 ShowMove(fromX, fromY, toX, toY)
8795 {
8796     int instant = (gameMode == PlayFromGameFile) ?
8797         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8798     if(appData.noGUI) return;
8799     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8800         if (!instant) {
8801             if (forwardMostMove == currentMove + 1) {
8802                 AnimateMove(boards[forwardMostMove - 1],
8803                             fromX, fromY, toX, toY);
8804             }
8805             if (appData.highlightLastMove) {
8806                 SetHighlights(fromX, fromY, toX, toY);
8807             }
8808         }
8809         currentMove = forwardMostMove;
8810     }
8811
8812     if (instant) return;
8813
8814     DisplayMove(currentMove - 1);
8815     DrawPosition(FALSE, boards[currentMove]);
8816     DisplayBothClocks();
8817     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8818 }
8819
8820 void SendEgtPath(ChessProgramState *cps)
8821 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8822         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8823
8824         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8825
8826         while(*p) {
8827             char c, *q = name+1, *r, *s;
8828
8829             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8830             while(*p && *p != ',') *q++ = *p++;
8831             *q++ = ':'; *q = 0;
8832             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8833                 strcmp(name, ",nalimov:") == 0 ) {
8834                 // take nalimov path from the menu-changeable option first, if it is defined
8835               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8836                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8837             } else
8838             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8839                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8840                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8841                 s = r = StrStr(s, ":") + 1; // beginning of path info
8842                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8843                 c = *r; *r = 0;             // temporarily null-terminate path info
8844                     *--q = 0;               // strip of trailig ':' from name
8845                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8846                 *r = c;
8847                 SendToProgram(buf,cps);     // send egtbpath command for this format
8848             }
8849             if(*p == ',') p++; // read away comma to position for next format name
8850         }
8851 }
8852
8853 void
8854 InitChessProgram(cps, setup)
8855      ChessProgramState *cps;
8856      int setup; /* [HGM] needed to setup FRC opening position */
8857 {
8858     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8859     if (appData.noChessProgram) return;
8860     hintRequested = FALSE;
8861     bookRequested = FALSE;
8862
8863     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8864     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8865     if(cps->memSize) { /* [HGM] memory */
8866       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8867         SendToProgram(buf, cps);
8868     }
8869     SendEgtPath(cps); /* [HGM] EGT */
8870     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8871       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8872         SendToProgram(buf, cps);
8873     }
8874
8875     SendToProgram(cps->initString, cps);
8876     if (gameInfo.variant != VariantNormal &&
8877         gameInfo.variant != VariantLoadable
8878         /* [HGM] also send variant if board size non-standard */
8879         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8880                                             ) {
8881       char *v = VariantName(gameInfo.variant);
8882       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8883         /* [HGM] in protocol 1 we have to assume all variants valid */
8884         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8885         DisplayFatalError(buf, 0, 1);
8886         return;
8887       }
8888
8889       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8890       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8891       if( gameInfo.variant == VariantXiangqi )
8892            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8893       if( gameInfo.variant == VariantShogi )
8894            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8895       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8896            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8897       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8898           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
8899            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8900       if( gameInfo.variant == VariantCourier )
8901            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8902       if( gameInfo.variant == VariantSuper )
8903            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8904       if( gameInfo.variant == VariantGreat )
8905            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8906       if( gameInfo.variant == VariantSChess )
8907            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8908
8909       if(overruled) {
8910         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8911                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8912            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8913            if(StrStr(cps->variants, b) == NULL) {
8914                // specific sized variant not known, check if general sizing allowed
8915                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8916                    if(StrStr(cps->variants, "boardsize") == NULL) {
8917                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8918                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8919                        DisplayFatalError(buf, 0, 1);
8920                        return;
8921                    }
8922                    /* [HGM] here we really should compare with the maximum supported board size */
8923                }
8924            }
8925       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8926       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8927       SendToProgram(buf, cps);
8928     }
8929     currentlyInitializedVariant = gameInfo.variant;
8930
8931     /* [HGM] send opening position in FRC to first engine */
8932     if(setup) {
8933           SendToProgram("force\n", cps);
8934           SendBoard(cps, 0);
8935           /* engine is now in force mode! Set flag to wake it up after first move. */
8936           setboardSpoiledMachineBlack = 1;
8937     }
8938
8939     if (cps->sendICS) {
8940       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8941       SendToProgram(buf, cps);
8942     }
8943     cps->maybeThinking = FALSE;
8944     cps->offeredDraw = 0;
8945     if (!appData.icsActive) {
8946         SendTimeControl(cps, movesPerSession, timeControl,
8947                         timeIncrement, appData.searchDepth,
8948                         searchTime);
8949     }
8950     if (appData.showThinking
8951         // [HGM] thinking: four options require thinking output to be sent
8952         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8953                                 ) {
8954         SendToProgram("post\n", cps);
8955     }
8956     SendToProgram("hard\n", cps);
8957     if (!appData.ponderNextMove) {
8958         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8959            it without being sure what state we are in first.  "hard"
8960            is not a toggle, so that one is OK.
8961          */
8962         SendToProgram("easy\n", cps);
8963     }
8964     if (cps->usePing) {
8965       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8966       SendToProgram(buf, cps);
8967     }
8968     cps->initDone = TRUE;
8969 }
8970
8971
8972 void
8973 StartChessProgram(cps)
8974      ChessProgramState *cps;
8975 {
8976     char buf[MSG_SIZ];
8977     int err;
8978
8979     if (appData.noChessProgram) return;
8980     cps->initDone = FALSE;
8981
8982     if (strcmp(cps->host, "localhost") == 0) {
8983         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8984     } else if (*appData.remoteShell == NULLCHAR) {
8985         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8986     } else {
8987         if (*appData.remoteUser == NULLCHAR) {
8988           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8989                     cps->program);
8990         } else {
8991           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8992                     cps->host, appData.remoteUser, cps->program);
8993         }
8994         err = StartChildProcess(buf, "", &cps->pr);
8995     }
8996
8997     if (err != 0) {
8998       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8999         DisplayFatalError(buf, err, 1);
9000         cps->pr = NoProc;
9001         cps->isr = NULL;
9002         return;
9003     }
9004
9005     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9006     if (cps->protocolVersion > 1) {
9007       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9008       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9009       cps->comboCnt = 0;  //                and values of combo boxes
9010       SendToProgram(buf, cps);
9011     } else {
9012       SendToProgram("xboard\n", cps);
9013     }
9014 }
9015
9016
9017 void
9018 TwoMachinesEventIfReady P((void))
9019 {
9020   if (first.lastPing != first.lastPong) {
9021     DisplayMessage("", _("Waiting for first chess program"));
9022     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9023     return;
9024   }
9025   if (second.lastPing != second.lastPong) {
9026     DisplayMessage("", _("Waiting for second chess program"));
9027     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9028     return;
9029   }
9030   ThawUI();
9031   TwoMachinesEvent();
9032 }
9033
9034 void
9035 NextMatchGame P((void))
9036 {
9037     int index; /* [HGM] autoinc: step load index during match */
9038     Reset(FALSE, TRUE);
9039     if (*appData.loadGameFile != NULLCHAR) {
9040         index = appData.loadGameIndex;
9041         if(index < 0) { // [HGM] autoinc
9042             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9043             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9044         }
9045         LoadGameFromFile(appData.loadGameFile,
9046                          index,
9047                          appData.loadGameFile, FALSE);
9048     } else if (*appData.loadPositionFile != NULLCHAR) {
9049         index = appData.loadPositionIndex;
9050         if(index < 0) { // [HGM] autoinc
9051             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9052             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9053         }
9054         LoadPositionFromFile(appData.loadPositionFile,
9055                              index,
9056                              appData.loadPositionFile);
9057     }
9058     TwoMachinesEventIfReady();
9059 }
9060
9061 void UserAdjudicationEvent( int result )
9062 {
9063     ChessMove gameResult = GameIsDrawn;
9064
9065     if( result > 0 ) {
9066         gameResult = WhiteWins;
9067     }
9068     else if( result < 0 ) {
9069         gameResult = BlackWins;
9070     }
9071
9072     if( gameMode == TwoMachinesPlay ) {
9073         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9074     }
9075 }
9076
9077
9078 // [HGM] save: calculate checksum of game to make games easily identifiable
9079 int StringCheckSum(char *s)
9080 {
9081         int i = 0;
9082         if(s==NULL) return 0;
9083         while(*s) i = i*259 + *s++;
9084         return i;
9085 }
9086
9087 int GameCheckSum()
9088 {
9089         int i, sum=0;
9090         for(i=backwardMostMove; i<forwardMostMove; i++) {
9091                 sum += pvInfoList[i].depth;
9092                 sum += StringCheckSum(parseList[i]);
9093                 sum += StringCheckSum(commentList[i]);
9094                 sum *= 261;
9095         }
9096         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9097         return sum + StringCheckSum(commentList[i]);
9098 } // end of save patch
9099
9100 void
9101 GameEnds(result, resultDetails, whosays)
9102      ChessMove result;
9103      char *resultDetails;
9104      int whosays;
9105 {
9106     GameMode nextGameMode;
9107     int isIcsGame;
9108     char buf[MSG_SIZ], popupRequested = 0;
9109
9110     if(endingGame) return; /* [HGM] crash: forbid recursion */
9111     endingGame = 1;
9112     if(twoBoards) { // [HGM] dual: switch back to one board
9113         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9114         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9115     }
9116     if (appData.debugMode) {
9117       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9118               result, resultDetails ? resultDetails : "(null)", whosays);
9119     }
9120
9121     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9122
9123     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9124         /* If we are playing on ICS, the server decides when the
9125            game is over, but the engine can offer to draw, claim
9126            a draw, or resign.
9127          */
9128 #if ZIPPY
9129         if (appData.zippyPlay && first.initDone) {
9130             if (result == GameIsDrawn) {
9131                 /* In case draw still needs to be claimed */
9132                 SendToICS(ics_prefix);
9133                 SendToICS("draw\n");
9134             } else if (StrCaseStr(resultDetails, "resign")) {
9135                 SendToICS(ics_prefix);
9136                 SendToICS("resign\n");
9137             }
9138         }
9139 #endif
9140         endingGame = 0; /* [HGM] crash */
9141         return;
9142     }
9143
9144     /* If we're loading the game from a file, stop */
9145     if (whosays == GE_FILE) {
9146       (void) StopLoadGameTimer();
9147       gameFileFP = NULL;
9148     }
9149
9150     /* Cancel draw offers */
9151     first.offeredDraw = second.offeredDraw = 0;
9152
9153     /* If this is an ICS game, only ICS can really say it's done;
9154        if not, anyone can. */
9155     isIcsGame = (gameMode == IcsPlayingWhite ||
9156                  gameMode == IcsPlayingBlack ||
9157                  gameMode == IcsObserving    ||
9158                  gameMode == IcsExamining);
9159
9160     if (!isIcsGame || whosays == GE_ICS) {
9161         /* OK -- not an ICS game, or ICS said it was done */
9162         StopClocks();
9163         if (!isIcsGame && !appData.noChessProgram)
9164           SetUserThinkingEnables();
9165
9166         /* [HGM] if a machine claims the game end we verify this claim */
9167         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9168             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9169                 char claimer;
9170                 ChessMove trueResult = (ChessMove) -1;
9171
9172                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9173                                             first.twoMachinesColor[0] :
9174                                             second.twoMachinesColor[0] ;
9175
9176                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9177                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9178                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9179                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9180                 } else
9181                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9182                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9183                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9184                 } else
9185                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9186                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9187                 }
9188
9189                 // now verify win claims, but not in drop games, as we don't understand those yet
9190                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9191                                                  || gameInfo.variant == VariantGreat) &&
9192                     (result == WhiteWins && claimer == 'w' ||
9193                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9194                       if (appData.debugMode) {
9195                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9196                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9197                       }
9198                       if(result != trueResult) {
9199                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9200                               result = claimer == 'w' ? BlackWins : WhiteWins;
9201                               resultDetails = buf;
9202                       }
9203                 } else
9204                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9205                     && (forwardMostMove <= backwardMostMove ||
9206                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9207                         (claimer=='b')==(forwardMostMove&1))
9208                                                                                   ) {
9209                       /* [HGM] verify: draws that were not flagged are false claims */
9210                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9211                       result = claimer == 'w' ? BlackWins : WhiteWins;
9212                       resultDetails = buf;
9213                 }
9214                 /* (Claiming a loss is accepted no questions asked!) */
9215             }
9216             /* [HGM] bare: don't allow bare King to win */
9217             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9218                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9219                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9220                && result != GameIsDrawn)
9221             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9222                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9223                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9224                         if(p >= 0 && p <= (int)WhiteKing) k++;
9225                 }
9226                 if (appData.debugMode) {
9227                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9228                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9229                 }
9230                 if(k <= 1) {
9231                         result = GameIsDrawn;
9232                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9233                         resultDetails = buf;
9234                 }
9235             }
9236         }
9237
9238
9239         if(serverMoves != NULL && !loadFlag) { char c = '=';
9240             if(result==WhiteWins) c = '+';
9241             if(result==BlackWins) c = '-';
9242             if(resultDetails != NULL)
9243                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9244         }
9245         if (resultDetails != NULL) {
9246             gameInfo.result = result;
9247             gameInfo.resultDetails = StrSave(resultDetails);
9248
9249             /* display last move only if game was not loaded from file */
9250             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9251                 DisplayMove(currentMove - 1);
9252
9253             if (forwardMostMove != 0) {
9254                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9255                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9256                                                                 ) {
9257                     if (*appData.saveGameFile != NULLCHAR) {
9258                         SaveGameToFile(appData.saveGameFile, TRUE);
9259                     } else if (appData.autoSaveGames) {
9260                         AutoSaveGame();
9261                     }
9262                     if (*appData.savePositionFile != NULLCHAR) {
9263                         SavePositionToFile(appData.savePositionFile);
9264                     }
9265                 }
9266             }
9267
9268             /* Tell program how game ended in case it is learning */
9269             /* [HGM] Moved this to after saving the PGN, just in case */
9270             /* engine died and we got here through time loss. In that */
9271             /* case we will get a fatal error writing the pipe, which */
9272             /* would otherwise lose us the PGN.                       */
9273             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9274             /* output during GameEnds should never be fatal anymore   */
9275             if (gameMode == MachinePlaysWhite ||
9276                 gameMode == MachinePlaysBlack ||
9277                 gameMode == TwoMachinesPlay ||
9278                 gameMode == IcsPlayingWhite ||
9279                 gameMode == IcsPlayingBlack ||
9280                 gameMode == BeginningOfGame) {
9281                 char buf[MSG_SIZ];
9282                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9283                         resultDetails);
9284                 if (first.pr != NoProc) {
9285                     SendToProgram(buf, &first);
9286                 }
9287                 if (second.pr != NoProc &&
9288                     gameMode == TwoMachinesPlay) {
9289                     SendToProgram(buf, &second);
9290                 }
9291             }
9292         }
9293
9294         if (appData.icsActive) {
9295             if (appData.quietPlay &&
9296                 (gameMode == IcsPlayingWhite ||
9297                  gameMode == IcsPlayingBlack)) {
9298                 SendToICS(ics_prefix);
9299                 SendToICS("set shout 1\n");
9300             }
9301             nextGameMode = IcsIdle;
9302             ics_user_moved = FALSE;
9303             /* clean up premove.  It's ugly when the game has ended and the
9304              * premove highlights are still on the board.
9305              */
9306             if (gotPremove) {
9307               gotPremove = FALSE;
9308               ClearPremoveHighlights();
9309               DrawPosition(FALSE, boards[currentMove]);
9310             }
9311             if (whosays == GE_ICS) {
9312                 switch (result) {
9313                 case WhiteWins:
9314                     if (gameMode == IcsPlayingWhite)
9315                         PlayIcsWinSound();
9316                     else if(gameMode == IcsPlayingBlack)
9317                         PlayIcsLossSound();
9318                     break;
9319                 case BlackWins:
9320                     if (gameMode == IcsPlayingBlack)
9321                         PlayIcsWinSound();
9322                     else if(gameMode == IcsPlayingWhite)
9323                         PlayIcsLossSound();
9324                     break;
9325                 case GameIsDrawn:
9326                     PlayIcsDrawSound();
9327                     break;
9328                 default:
9329                     PlayIcsUnfinishedSound();
9330                 }
9331             }
9332         } else if (gameMode == EditGame ||
9333                    gameMode == PlayFromGameFile ||
9334                    gameMode == AnalyzeMode ||
9335                    gameMode == AnalyzeFile) {
9336             nextGameMode = gameMode;
9337         } else {
9338             nextGameMode = EndOfGame;
9339         }
9340         pausing = FALSE;
9341         ModeHighlight();
9342     } else {
9343         nextGameMode = gameMode;
9344     }
9345
9346     if (appData.noChessProgram) {
9347         gameMode = nextGameMode;
9348         ModeHighlight();
9349         endingGame = 0; /* [HGM] crash */
9350         return;
9351     }
9352
9353     if (first.reuse) {
9354         /* Put first chess program into idle state */
9355         if (first.pr != NoProc &&
9356             (gameMode == MachinePlaysWhite ||
9357              gameMode == MachinePlaysBlack ||
9358              gameMode == TwoMachinesPlay ||
9359              gameMode == IcsPlayingWhite ||
9360              gameMode == IcsPlayingBlack ||
9361              gameMode == BeginningOfGame)) {
9362             SendToProgram("force\n", &first);
9363             if (first.usePing) {
9364               char buf[MSG_SIZ];
9365               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9366               SendToProgram(buf, &first);
9367             }
9368         }
9369     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9370         /* Kill off first chess program */
9371         if (first.isr != NULL)
9372           RemoveInputSource(first.isr);
9373         first.isr = NULL;
9374
9375         if (first.pr != NoProc) {
9376             ExitAnalyzeMode();
9377             DoSleep( appData.delayBeforeQuit );
9378             SendToProgram("quit\n", &first);
9379             DoSleep( appData.delayAfterQuit );
9380             DestroyChildProcess(first.pr, first.useSigterm);
9381         }
9382         first.pr = NoProc;
9383     }
9384     if (second.reuse) {
9385         /* Put second chess program into idle state */
9386         if (second.pr != NoProc &&
9387             gameMode == TwoMachinesPlay) {
9388             SendToProgram("force\n", &second);
9389             if (second.usePing) {
9390               char buf[MSG_SIZ];
9391               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9392               SendToProgram(buf, &second);
9393             }
9394         }
9395     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9396         /* Kill off second chess program */
9397         if (second.isr != NULL)
9398           RemoveInputSource(second.isr);
9399         second.isr = NULL;
9400
9401         if (second.pr != NoProc) {
9402             DoSleep( appData.delayBeforeQuit );
9403             SendToProgram("quit\n", &second);
9404             DoSleep( appData.delayAfterQuit );
9405             DestroyChildProcess(second.pr, second.useSigterm);
9406         }
9407         second.pr = NoProc;
9408     }
9409
9410     if (matchMode && gameMode == TwoMachinesPlay) {
9411         switch (result) {
9412         case WhiteWins:
9413           if (first.twoMachinesColor[0] == 'w') {
9414             first.matchWins++;
9415           } else {
9416             second.matchWins++;
9417           }
9418           break;
9419         case BlackWins:
9420           if (first.twoMachinesColor[0] == 'b') {
9421             first.matchWins++;
9422           } else {
9423             second.matchWins++;
9424           }
9425           break;
9426         default:
9427           break;
9428         }
9429         if (matchGame < appData.matchGames) {
9430             char *tmp;
9431             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9432                 tmp = first.twoMachinesColor;
9433                 first.twoMachinesColor = second.twoMachinesColor;
9434                 second.twoMachinesColor = tmp;
9435             }
9436             gameMode = nextGameMode;
9437             matchGame++;
9438             if(appData.matchPause>10000 || appData.matchPause<10)
9439                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9440             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9441             endingGame = 0; /* [HGM] crash */
9442             return;
9443         } else {
9444             gameMode = nextGameMode;
9445             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9446                      first.tidy, second.tidy,
9447                      first.matchWins, second.matchWins,
9448                      appData.matchGames - (first.matchWins + second.matchWins));
9449             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9450             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9451                 first.twoMachinesColor = "black\n";
9452                 second.twoMachinesColor = "white\n";
9453             } else {
9454                 first.twoMachinesColor = "white\n";
9455                 second.twoMachinesColor = "black\n";
9456             }
9457         }
9458     }
9459     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9460         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9461       ExitAnalyzeMode();
9462     gameMode = nextGameMode;
9463     ModeHighlight();
9464     endingGame = 0;  /* [HGM] crash */
9465     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9466       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9467         matchMode = FALSE; appData.matchGames = matchGame = 0;
9468         DisplayNote(buf);
9469       }
9470     }
9471 }
9472
9473 /* Assumes program was just initialized (initString sent).
9474    Leaves program in force mode. */
9475 void
9476 FeedMovesToProgram(cps, upto)
9477      ChessProgramState *cps;
9478      int upto;
9479 {
9480     int i;
9481
9482     if (appData.debugMode)
9483       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9484               startedFromSetupPosition ? "position and " : "",
9485               backwardMostMove, upto, cps->which);
9486     if(currentlyInitializedVariant != gameInfo.variant) {
9487       char buf[MSG_SIZ];
9488         // [HGM] variantswitch: make engine aware of new variant
9489         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9490                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9491         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9492         SendToProgram(buf, cps);
9493         currentlyInitializedVariant = gameInfo.variant;
9494     }
9495     SendToProgram("force\n", cps);
9496     if (startedFromSetupPosition) {
9497         SendBoard(cps, backwardMostMove);
9498     if (appData.debugMode) {
9499         fprintf(debugFP, "feedMoves\n");
9500     }
9501     }
9502     for (i = backwardMostMove; i < upto; i++) {
9503         SendMoveToProgram(i, cps);
9504     }
9505 }
9506
9507
9508 void
9509 ResurrectChessProgram()
9510 {
9511      /* The chess program may have exited.
9512         If so, restart it and feed it all the moves made so far. */
9513
9514     if (appData.noChessProgram || first.pr != NoProc) return;
9515
9516     StartChessProgram(&first);
9517     InitChessProgram(&first, FALSE);
9518     FeedMovesToProgram(&first, currentMove);
9519
9520     if (!first.sendTime) {
9521         /* can't tell gnuchess what its clock should read,
9522            so we bow to its notion. */
9523         ResetClocks();
9524         timeRemaining[0][currentMove] = whiteTimeRemaining;
9525         timeRemaining[1][currentMove] = blackTimeRemaining;
9526     }
9527
9528     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9529                 appData.icsEngineAnalyze) && first.analysisSupport) {
9530       SendToProgram("analyze\n", &first);
9531       first.analyzing = TRUE;
9532     }
9533 }
9534
9535 /*
9536  * Button procedures
9537  */
9538 void
9539 Reset(redraw, init)
9540      int redraw, init;
9541 {
9542     int i;
9543
9544     if (appData.debugMode) {
9545         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9546                 redraw, init, gameMode);
9547     }
9548     CleanupTail(); // [HGM] vari: delete any stored variations
9549     pausing = pauseExamInvalid = FALSE;
9550     startedFromSetupPosition = blackPlaysFirst = FALSE;
9551     firstMove = TRUE;
9552     whiteFlag = blackFlag = FALSE;
9553     userOfferedDraw = FALSE;
9554     hintRequested = bookRequested = FALSE;
9555     first.maybeThinking = FALSE;
9556     second.maybeThinking = FALSE;
9557     first.bookSuspend = FALSE; // [HGM] book
9558     second.bookSuspend = FALSE;
9559     thinkOutput[0] = NULLCHAR;
9560     lastHint[0] = NULLCHAR;
9561     ClearGameInfo(&gameInfo);
9562     gameInfo.variant = StringToVariant(appData.variant);
9563     ics_user_moved = ics_clock_paused = FALSE;
9564     ics_getting_history = H_FALSE;
9565     ics_gamenum = -1;
9566     white_holding[0] = black_holding[0] = NULLCHAR;
9567     ClearProgramStats();
9568     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9569
9570     ResetFrontEnd();
9571     ClearHighlights();
9572     flipView = appData.flipView;
9573     ClearPremoveHighlights();
9574     gotPremove = FALSE;
9575     alarmSounded = FALSE;
9576
9577     GameEnds(EndOfFile, NULL, GE_PLAYER);
9578     if(appData.serverMovesName != NULL) {
9579         /* [HGM] prepare to make moves file for broadcasting */
9580         clock_t t = clock();
9581         if(serverMoves != NULL) fclose(serverMoves);
9582         serverMoves = fopen(appData.serverMovesName, "r");
9583         if(serverMoves != NULL) {
9584             fclose(serverMoves);
9585             /* delay 15 sec before overwriting, so all clients can see end */
9586             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9587         }
9588         serverMoves = fopen(appData.serverMovesName, "w");
9589     }
9590
9591     ExitAnalyzeMode();
9592     gameMode = BeginningOfGame;
9593     ModeHighlight();
9594     if(appData.icsActive) gameInfo.variant = VariantNormal;
9595     currentMove = forwardMostMove = backwardMostMove = 0;
9596     InitPosition(redraw);
9597     for (i = 0; i < MAX_MOVES; i++) {
9598         if (commentList[i] != NULL) {
9599             free(commentList[i]);
9600             commentList[i] = NULL;
9601         }
9602     }
9603     ResetClocks();
9604     timeRemaining[0][0] = whiteTimeRemaining;
9605     timeRemaining[1][0] = blackTimeRemaining;
9606     if (first.pr == NULL) {
9607         StartChessProgram(&first);
9608     }
9609     if (init) {
9610             InitChessProgram(&first, startedFromSetupPosition);
9611     }
9612     DisplayTitle("");
9613     DisplayMessage("", "");
9614     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9615     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9616 }
9617
9618 void
9619 AutoPlayGameLoop()
9620 {
9621     for (;;) {
9622         if (!AutoPlayOneMove())
9623           return;
9624         if (matchMode || appData.timeDelay == 0)
9625           continue;
9626         if (appData.timeDelay < 0)
9627           return;
9628         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9629         break;
9630     }
9631 }
9632
9633
9634 int
9635 AutoPlayOneMove()
9636 {
9637     int fromX, fromY, toX, toY;
9638
9639     if (appData.debugMode) {
9640       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9641     }
9642
9643     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9644       return FALSE;
9645
9646     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9647       pvInfoList[currentMove].depth = programStats.depth;
9648       pvInfoList[currentMove].score = programStats.score;
9649       pvInfoList[currentMove].time  = 0;
9650       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9651     }
9652
9653     if (currentMove >= forwardMostMove) {
9654       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9655       gameMode = EditGame;
9656       ModeHighlight();
9657
9658       /* [AS] Clear current move marker at the end of a game */
9659       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9660
9661       return FALSE;
9662     }
9663
9664     toX = moveList[currentMove][2] - AAA;
9665     toY = moveList[currentMove][3] - ONE;
9666
9667     if (moveList[currentMove][1] == '@') {
9668         if (appData.highlightLastMove) {
9669             SetHighlights(-1, -1, toX, toY);
9670         }
9671     } else {
9672         fromX = moveList[currentMove][0] - AAA;
9673         fromY = moveList[currentMove][1] - ONE;
9674
9675         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9676
9677         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9678
9679         if (appData.highlightLastMove) {
9680             SetHighlights(fromX, fromY, toX, toY);
9681         }
9682     }
9683     DisplayMove(currentMove);
9684     SendMoveToProgram(currentMove++, &first);
9685     DisplayBothClocks();
9686     DrawPosition(FALSE, boards[currentMove]);
9687     // [HGM] PV info: always display, routine tests if empty
9688     DisplayComment(currentMove - 1, commentList[currentMove]);
9689     return TRUE;
9690 }
9691
9692
9693 int
9694 LoadGameOneMove(readAhead)
9695      ChessMove readAhead;
9696 {
9697     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9698     char promoChar = NULLCHAR;
9699     ChessMove moveType;
9700     char move[MSG_SIZ];
9701     char *p, *q;
9702
9703     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9704         gameMode != AnalyzeMode && gameMode != Training) {
9705         gameFileFP = NULL;
9706         return FALSE;
9707     }
9708
9709     yyboardindex = forwardMostMove;
9710     if (readAhead != EndOfFile) {
9711       moveType = readAhead;
9712     } else {
9713       if (gameFileFP == NULL)
9714           return FALSE;
9715       moveType = (ChessMove) Myylex();
9716     }
9717
9718     done = FALSE;
9719     switch (moveType) {
9720       case Comment:
9721         if (appData.debugMode)
9722           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9723         p = yy_text;
9724
9725         /* append the comment but don't display it */
9726         AppendComment(currentMove, p, FALSE);
9727         return TRUE;
9728
9729       case WhiteCapturesEnPassant:
9730       case BlackCapturesEnPassant:
9731       case WhitePromotion:
9732       case BlackPromotion:
9733       case WhiteNonPromotion:
9734       case BlackNonPromotion:
9735       case NormalMove:
9736       case WhiteKingSideCastle:
9737       case WhiteQueenSideCastle:
9738       case BlackKingSideCastle:
9739       case BlackQueenSideCastle:
9740       case WhiteKingSideCastleWild:
9741       case WhiteQueenSideCastleWild:
9742       case BlackKingSideCastleWild:
9743       case BlackQueenSideCastleWild:
9744       /* PUSH Fabien */
9745       case WhiteHSideCastleFR:
9746       case WhiteASideCastleFR:
9747       case BlackHSideCastleFR:
9748       case BlackASideCastleFR:
9749       /* POP Fabien */
9750         if (appData.debugMode)
9751           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9752         fromX = currentMoveString[0] - AAA;
9753         fromY = currentMoveString[1] - ONE;
9754         toX = currentMoveString[2] - AAA;
9755         toY = currentMoveString[3] - ONE;
9756         promoChar = currentMoveString[4];
9757         break;
9758
9759       case WhiteDrop:
9760       case BlackDrop:
9761         if (appData.debugMode)
9762           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9763         fromX = moveType == WhiteDrop ?
9764           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9765         (int) CharToPiece(ToLower(currentMoveString[0]));
9766         fromY = DROP_RANK;
9767         toX = currentMoveString[2] - AAA;
9768         toY = currentMoveString[3] - ONE;
9769         break;
9770
9771       case WhiteWins:
9772       case BlackWins:
9773       case GameIsDrawn:
9774       case GameUnfinished:
9775         if (appData.debugMode)
9776           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9777         p = strchr(yy_text, '{');
9778         if (p == NULL) p = strchr(yy_text, '(');
9779         if (p == NULL) {
9780             p = yy_text;
9781             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9782         } else {
9783             q = strchr(p, *p == '{' ? '}' : ')');
9784             if (q != NULL) *q = NULLCHAR;
9785             p++;
9786         }
9787         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9788         GameEnds(moveType, p, GE_FILE);
9789         done = TRUE;
9790         if (cmailMsgLoaded) {
9791             ClearHighlights();
9792             flipView = WhiteOnMove(currentMove);
9793             if (moveType == GameUnfinished) flipView = !flipView;
9794             if (appData.debugMode)
9795               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9796         }
9797         break;
9798
9799       case EndOfFile:
9800         if (appData.debugMode)
9801           fprintf(debugFP, "Parser hit end of file\n");
9802         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9803           case MT_NONE:
9804           case MT_CHECK:
9805             break;
9806           case MT_CHECKMATE:
9807           case MT_STAINMATE:
9808             if (WhiteOnMove(currentMove)) {
9809                 GameEnds(BlackWins, "Black mates", GE_FILE);
9810             } else {
9811                 GameEnds(WhiteWins, "White mates", GE_FILE);
9812             }
9813             break;
9814           case MT_STALEMATE:
9815             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9816             break;
9817         }
9818         done = TRUE;
9819         break;
9820
9821       case MoveNumberOne:
9822         if (lastLoadGameStart == GNUChessGame) {
9823             /* GNUChessGames have numbers, but they aren't move numbers */
9824             if (appData.debugMode)
9825               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9826                       yy_text, (int) moveType);
9827             return LoadGameOneMove(EndOfFile); /* tail recursion */
9828         }
9829         /* else fall thru */
9830
9831       case XBoardGame:
9832       case GNUChessGame:
9833       case PGNTag:
9834         /* Reached start of next game in file */
9835         if (appData.debugMode)
9836           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9837         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9838           case MT_NONE:
9839           case MT_CHECK:
9840             break;
9841           case MT_CHECKMATE:
9842           case MT_STAINMATE:
9843             if (WhiteOnMove(currentMove)) {
9844                 GameEnds(BlackWins, "Black mates", GE_FILE);
9845             } else {
9846                 GameEnds(WhiteWins, "White mates", GE_FILE);
9847             }
9848             break;
9849           case MT_STALEMATE:
9850             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9851             break;
9852         }
9853         done = TRUE;
9854         break;
9855
9856       case PositionDiagram:     /* should not happen; ignore */
9857       case ElapsedTime:         /* ignore */
9858       case NAG:                 /* ignore */
9859         if (appData.debugMode)
9860           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9861                   yy_text, (int) moveType);
9862         return LoadGameOneMove(EndOfFile); /* tail recursion */
9863
9864       case IllegalMove:
9865         if (appData.testLegality) {
9866             if (appData.debugMode)
9867               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9868             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9869                     (forwardMostMove / 2) + 1,
9870                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9871             DisplayError(move, 0);
9872             done = TRUE;
9873         } else {
9874             if (appData.debugMode)
9875               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9876                       yy_text, currentMoveString);
9877             fromX = currentMoveString[0] - AAA;
9878             fromY = currentMoveString[1] - ONE;
9879             toX = currentMoveString[2] - AAA;
9880             toY = currentMoveString[3] - ONE;
9881             promoChar = currentMoveString[4];
9882         }
9883         break;
9884
9885       case AmbiguousMove:
9886         if (appData.debugMode)
9887           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9888         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9889                 (forwardMostMove / 2) + 1,
9890                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9891         DisplayError(move, 0);
9892         done = TRUE;
9893         break;
9894
9895       default:
9896       case ImpossibleMove:
9897         if (appData.debugMode)
9898           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9899         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9900                 (forwardMostMove / 2) + 1,
9901                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9902         DisplayError(move, 0);
9903         done = TRUE;
9904         break;
9905     }
9906
9907     if (done) {
9908         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9909             DrawPosition(FALSE, boards[currentMove]);
9910             DisplayBothClocks();
9911             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9912               DisplayComment(currentMove - 1, commentList[currentMove]);
9913         }
9914         (void) StopLoadGameTimer();
9915         gameFileFP = NULL;
9916         cmailOldMove = forwardMostMove;
9917         return FALSE;
9918     } else {
9919         /* currentMoveString is set as a side-effect of yylex */
9920
9921         thinkOutput[0] = NULLCHAR;
9922         MakeMove(fromX, fromY, toX, toY, promoChar);
9923         currentMove = forwardMostMove;
9924         return TRUE;
9925     }
9926 }
9927
9928 /* Load the nth game from the given file */
9929 int
9930 LoadGameFromFile(filename, n, title, useList)
9931      char *filename;
9932      int n;
9933      char *title;
9934      /*Boolean*/ int useList;
9935 {
9936     FILE *f;
9937     char buf[MSG_SIZ];
9938
9939     if (strcmp(filename, "-") == 0) {
9940         f = stdin;
9941         title = "stdin";
9942     } else {
9943         f = fopen(filename, "rb");
9944         if (f == NULL) {
9945           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9946             DisplayError(buf, errno);
9947             return FALSE;
9948         }
9949     }
9950     if (fseek(f, 0, 0) == -1) {
9951         /* f is not seekable; probably a pipe */
9952         useList = FALSE;
9953     }
9954     if (useList && n == 0) {
9955         int error = GameListBuild(f);
9956         if (error) {
9957             DisplayError(_("Cannot build game list"), error);
9958         } else if (!ListEmpty(&gameList) &&
9959                    ((ListGame *) gameList.tailPred)->number > 1) {
9960             GameListPopUp(f, title);
9961             return TRUE;
9962         }
9963         GameListDestroy();
9964         n = 1;
9965     }
9966     if (n == 0) n = 1;
9967     return LoadGame(f, n, title, FALSE);
9968 }
9969
9970
9971 void
9972 MakeRegisteredMove()
9973 {
9974     int fromX, fromY, toX, toY;
9975     char promoChar;
9976     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9977         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9978           case CMAIL_MOVE:
9979           case CMAIL_DRAW:
9980             if (appData.debugMode)
9981               fprintf(debugFP, "Restoring %s for game %d\n",
9982                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9983
9984             thinkOutput[0] = NULLCHAR;
9985             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9986             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9987             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9988             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9989             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9990             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9991             MakeMove(fromX, fromY, toX, toY, promoChar);
9992             ShowMove(fromX, fromY, toX, toY);
9993
9994             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9995               case MT_NONE:
9996               case MT_CHECK:
9997                 break;
9998
9999               case MT_CHECKMATE:
10000               case MT_STAINMATE:
10001                 if (WhiteOnMove(currentMove)) {
10002                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10003                 } else {
10004                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10005                 }
10006                 break;
10007
10008               case MT_STALEMATE:
10009                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10010                 break;
10011             }
10012
10013             break;
10014
10015           case CMAIL_RESIGN:
10016             if (WhiteOnMove(currentMove)) {
10017                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10018             } else {
10019                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10020             }
10021             break;
10022
10023           case CMAIL_ACCEPT:
10024             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10025             break;
10026
10027           default:
10028             break;
10029         }
10030     }
10031
10032     return;
10033 }
10034
10035 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10036 int
10037 CmailLoadGame(f, gameNumber, title, useList)
10038      FILE *f;
10039      int gameNumber;
10040      char *title;
10041      int useList;
10042 {
10043     int retVal;
10044
10045     if (gameNumber > nCmailGames) {
10046         DisplayError(_("No more games in this message"), 0);
10047         return FALSE;
10048     }
10049     if (f == lastLoadGameFP) {
10050         int offset = gameNumber - lastLoadGameNumber;
10051         if (offset == 0) {
10052             cmailMsg[0] = NULLCHAR;
10053             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10054                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10055                 nCmailMovesRegistered--;
10056             }
10057             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10058             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10059                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10060             }
10061         } else {
10062             if (! RegisterMove()) return FALSE;
10063         }
10064     }
10065
10066     retVal = LoadGame(f, gameNumber, title, useList);
10067
10068     /* Make move registered during previous look at this game, if any */
10069     MakeRegisteredMove();
10070
10071     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10072         commentList[currentMove]
10073           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10074         DisplayComment(currentMove - 1, commentList[currentMove]);
10075     }
10076
10077     return retVal;
10078 }
10079
10080 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10081 int
10082 ReloadGame(offset)
10083      int offset;
10084 {
10085     int gameNumber = lastLoadGameNumber + offset;
10086     if (lastLoadGameFP == NULL) {
10087         DisplayError(_("No game has been loaded yet"), 0);
10088         return FALSE;
10089     }
10090     if (gameNumber <= 0) {
10091         DisplayError(_("Can't back up any further"), 0);
10092         return FALSE;
10093     }
10094     if (cmailMsgLoaded) {
10095         return CmailLoadGame(lastLoadGameFP, gameNumber,
10096                              lastLoadGameTitle, lastLoadGameUseList);
10097     } else {
10098         return LoadGame(lastLoadGameFP, gameNumber,
10099                         lastLoadGameTitle, lastLoadGameUseList);
10100     }
10101 }
10102
10103
10104
10105 /* Load the nth game from open file f */
10106 int
10107 LoadGame(f, gameNumber, title, useList)
10108      FILE *f;
10109      int gameNumber;
10110      char *title;
10111      int useList;
10112 {
10113     ChessMove cm;
10114     char buf[MSG_SIZ];
10115     int gn = gameNumber;
10116     ListGame *lg = NULL;
10117     int numPGNTags = 0;
10118     int err;
10119     GameMode oldGameMode;
10120     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10121
10122     if (appData.debugMode)
10123         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10124
10125     if (gameMode == Training )
10126         SetTrainingModeOff();
10127
10128     oldGameMode = gameMode;
10129     if (gameMode != BeginningOfGame) {
10130       Reset(FALSE, TRUE);
10131     }
10132
10133     gameFileFP = f;
10134     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10135         fclose(lastLoadGameFP);
10136     }
10137
10138     if (useList) {
10139         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10140
10141         if (lg) {
10142             fseek(f, lg->offset, 0);
10143             GameListHighlight(gameNumber);
10144             gn = 1;
10145         }
10146         else {
10147             DisplayError(_("Game number out of range"), 0);
10148             return FALSE;
10149         }
10150     } else {
10151         GameListDestroy();
10152         if (fseek(f, 0, 0) == -1) {
10153             if (f == lastLoadGameFP ?
10154                 gameNumber == lastLoadGameNumber + 1 :
10155                 gameNumber == 1) {
10156                 gn = 1;
10157             } else {
10158                 DisplayError(_("Can't seek on game file"), 0);
10159                 return FALSE;
10160             }
10161         }
10162     }
10163     lastLoadGameFP = f;
10164     lastLoadGameNumber = gameNumber;
10165     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10166     lastLoadGameUseList = useList;
10167
10168     yynewfile(f);
10169
10170     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10171       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10172                 lg->gameInfo.black);
10173             DisplayTitle(buf);
10174     } else if (*title != NULLCHAR) {
10175         if (gameNumber > 1) {
10176           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10177             DisplayTitle(buf);
10178         } else {
10179             DisplayTitle(title);
10180         }
10181     }
10182
10183     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10184         gameMode = PlayFromGameFile;
10185         ModeHighlight();
10186     }
10187
10188     currentMove = forwardMostMove = backwardMostMove = 0;
10189     CopyBoard(boards[0], initialPosition);
10190     StopClocks();
10191
10192     /*
10193      * Skip the first gn-1 games in the file.
10194      * Also skip over anything that precedes an identifiable
10195      * start of game marker, to avoid being confused by
10196      * garbage at the start of the file.  Currently
10197      * recognized start of game markers are the move number "1",
10198      * the pattern "gnuchess .* game", the pattern
10199      * "^[#;%] [^ ]* game file", and a PGN tag block.
10200      * A game that starts with one of the latter two patterns
10201      * will also have a move number 1, possibly
10202      * following a position diagram.
10203      * 5-4-02: Let's try being more lenient and allowing a game to
10204      * start with an unnumbered move.  Does that break anything?
10205      */
10206     cm = lastLoadGameStart = EndOfFile;
10207     while (gn > 0) {
10208         yyboardindex = forwardMostMove;
10209         cm = (ChessMove) Myylex();
10210         switch (cm) {
10211           case EndOfFile:
10212             if (cmailMsgLoaded) {
10213                 nCmailGames = CMAIL_MAX_GAMES - gn;
10214             } else {
10215                 Reset(TRUE, TRUE);
10216                 DisplayError(_("Game not found in file"), 0);
10217             }
10218             return FALSE;
10219
10220           case GNUChessGame:
10221           case XBoardGame:
10222             gn--;
10223             lastLoadGameStart = cm;
10224             break;
10225
10226           case MoveNumberOne:
10227             switch (lastLoadGameStart) {
10228               case GNUChessGame:
10229               case XBoardGame:
10230               case PGNTag:
10231                 break;
10232               case MoveNumberOne:
10233               case EndOfFile:
10234                 gn--;           /* count this game */
10235                 lastLoadGameStart = cm;
10236                 break;
10237               default:
10238                 /* impossible */
10239                 break;
10240             }
10241             break;
10242
10243           case PGNTag:
10244             switch (lastLoadGameStart) {
10245               case GNUChessGame:
10246               case PGNTag:
10247               case MoveNumberOne:
10248               case EndOfFile:
10249                 gn--;           /* count this game */
10250                 lastLoadGameStart = cm;
10251                 break;
10252               case XBoardGame:
10253                 lastLoadGameStart = cm; /* game counted already */
10254                 break;
10255               default:
10256                 /* impossible */
10257                 break;
10258             }
10259             if (gn > 0) {
10260                 do {
10261                     yyboardindex = forwardMostMove;
10262                     cm = (ChessMove) Myylex();
10263                 } while (cm == PGNTag || cm == Comment);
10264             }
10265             break;
10266
10267           case WhiteWins:
10268           case BlackWins:
10269           case GameIsDrawn:
10270             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10271                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10272                     != CMAIL_OLD_RESULT) {
10273                     nCmailResults ++ ;
10274                     cmailResult[  CMAIL_MAX_GAMES
10275                                 - gn - 1] = CMAIL_OLD_RESULT;
10276                 }
10277             }
10278             break;
10279
10280           case NormalMove:
10281             /* Only a NormalMove can be at the start of a game
10282              * without a position diagram. */
10283             if (lastLoadGameStart == EndOfFile ) {
10284               gn--;
10285               lastLoadGameStart = MoveNumberOne;
10286             }
10287             break;
10288
10289           default:
10290             break;
10291         }
10292     }
10293
10294     if (appData.debugMode)
10295       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10296
10297     if (cm == XBoardGame) {
10298         /* Skip any header junk before position diagram and/or move 1 */
10299         for (;;) {
10300             yyboardindex = forwardMostMove;
10301             cm = (ChessMove) Myylex();
10302
10303             if (cm == EndOfFile ||
10304                 cm == GNUChessGame || cm == XBoardGame) {
10305                 /* Empty game; pretend end-of-file and handle later */
10306                 cm = EndOfFile;
10307                 break;
10308             }
10309
10310             if (cm == MoveNumberOne || cm == PositionDiagram ||
10311                 cm == PGNTag || cm == Comment)
10312               break;
10313         }
10314     } else if (cm == GNUChessGame) {
10315         if (gameInfo.event != NULL) {
10316             free(gameInfo.event);
10317         }
10318         gameInfo.event = StrSave(yy_text);
10319     }
10320
10321     startedFromSetupPosition = FALSE;
10322     while (cm == PGNTag) {
10323         if (appData.debugMode)
10324           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10325         err = ParsePGNTag(yy_text, &gameInfo);
10326         if (!err) numPGNTags++;
10327
10328         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10329         if(gameInfo.variant != oldVariant) {
10330             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10331             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10332             InitPosition(TRUE);
10333             oldVariant = gameInfo.variant;
10334             if (appData.debugMode)
10335               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10336         }
10337
10338
10339         if (gameInfo.fen != NULL) {
10340           Board initial_position;
10341           startedFromSetupPosition = TRUE;
10342           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10343             Reset(TRUE, TRUE);
10344             DisplayError(_("Bad FEN position in file"), 0);
10345             return FALSE;
10346           }
10347           CopyBoard(boards[0], initial_position);
10348           if (blackPlaysFirst) {
10349             currentMove = forwardMostMove = backwardMostMove = 1;
10350             CopyBoard(boards[1], initial_position);
10351             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10352             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10353             timeRemaining[0][1] = whiteTimeRemaining;
10354             timeRemaining[1][1] = blackTimeRemaining;
10355             if (commentList[0] != NULL) {
10356               commentList[1] = commentList[0];
10357               commentList[0] = NULL;
10358             }
10359           } else {
10360             currentMove = forwardMostMove = backwardMostMove = 0;
10361           }
10362           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10363           {   int i;
10364               initialRulePlies = FENrulePlies;
10365               for( i=0; i< nrCastlingRights; i++ )
10366                   initialRights[i] = initial_position[CASTLING][i];
10367           }
10368           yyboardindex = forwardMostMove;
10369           free(gameInfo.fen);
10370           gameInfo.fen = NULL;
10371         }
10372
10373         yyboardindex = forwardMostMove;
10374         cm = (ChessMove) Myylex();
10375
10376         /* Handle comments interspersed among the tags */
10377         while (cm == Comment) {
10378             char *p;
10379             if (appData.debugMode)
10380               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10381             p = yy_text;
10382             AppendComment(currentMove, p, FALSE);
10383             yyboardindex = forwardMostMove;
10384             cm = (ChessMove) Myylex();
10385         }
10386     }
10387
10388     /* don't rely on existence of Event tag since if game was
10389      * pasted from clipboard the Event tag may not exist
10390      */
10391     if (numPGNTags > 0){
10392         char *tags;
10393         if (gameInfo.variant == VariantNormal) {
10394           VariantClass v = StringToVariant(gameInfo.event);
10395           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10396           if(v < VariantShogi) gameInfo.variant = v;
10397         }
10398         if (!matchMode) {
10399           if( appData.autoDisplayTags ) {
10400             tags = PGNTags(&gameInfo);
10401             TagsPopUp(tags, CmailMsg());
10402             free(tags);
10403           }
10404         }
10405     } else {
10406         /* Make something up, but don't display it now */
10407         SetGameInfo();
10408         TagsPopDown();
10409     }
10410
10411     if (cm == PositionDiagram) {
10412         int i, j;
10413         char *p;
10414         Board initial_position;
10415
10416         if (appData.debugMode)
10417           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10418
10419         if (!startedFromSetupPosition) {
10420             p = yy_text;
10421             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10422               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10423                 switch (*p) {
10424                   case '{':
10425                   case '[':
10426                   case '-':
10427                   case ' ':
10428                   case '\t':
10429                   case '\n':
10430                   case '\r':
10431                     break;
10432                   default:
10433                     initial_position[i][j++] = CharToPiece(*p);
10434                     break;
10435                 }
10436             while (*p == ' ' || *p == '\t' ||
10437                    *p == '\n' || *p == '\r') p++;
10438
10439             if (strncmp(p, "black", strlen("black"))==0)
10440               blackPlaysFirst = TRUE;
10441             else
10442               blackPlaysFirst = FALSE;
10443             startedFromSetupPosition = TRUE;
10444
10445             CopyBoard(boards[0], initial_position);
10446             if (blackPlaysFirst) {
10447                 currentMove = forwardMostMove = backwardMostMove = 1;
10448                 CopyBoard(boards[1], initial_position);
10449                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10450                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10451                 timeRemaining[0][1] = whiteTimeRemaining;
10452                 timeRemaining[1][1] = blackTimeRemaining;
10453                 if (commentList[0] != NULL) {
10454                     commentList[1] = commentList[0];
10455                     commentList[0] = NULL;
10456                 }
10457             } else {
10458                 currentMove = forwardMostMove = backwardMostMove = 0;
10459             }
10460         }
10461         yyboardindex = forwardMostMove;
10462         cm = (ChessMove) Myylex();
10463     }
10464
10465     if (first.pr == NoProc) {
10466         StartChessProgram(&first);
10467     }
10468     InitChessProgram(&first, FALSE);
10469     SendToProgram("force\n", &first);
10470     if (startedFromSetupPosition) {
10471         SendBoard(&first, forwardMostMove);
10472     if (appData.debugMode) {
10473         fprintf(debugFP, "Load Game\n");
10474     }
10475         DisplayBothClocks();
10476     }
10477
10478     /* [HGM] server: flag to write setup moves in broadcast file as one */
10479     loadFlag = appData.suppressLoadMoves;
10480
10481     while (cm == Comment) {
10482         char *p;
10483         if (appData.debugMode)
10484           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10485         p = yy_text;
10486         AppendComment(currentMove, p, FALSE);
10487         yyboardindex = forwardMostMove;
10488         cm = (ChessMove) Myylex();
10489     }
10490
10491     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10492         cm == WhiteWins || cm == BlackWins ||
10493         cm == GameIsDrawn || cm == GameUnfinished) {
10494         DisplayMessage("", _("No moves in game"));
10495         if (cmailMsgLoaded) {
10496             if (appData.debugMode)
10497               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10498             ClearHighlights();
10499             flipView = FALSE;
10500         }
10501         DrawPosition(FALSE, boards[currentMove]);
10502         DisplayBothClocks();
10503         gameMode = EditGame;
10504         ModeHighlight();
10505         gameFileFP = NULL;
10506         cmailOldMove = 0;
10507         return TRUE;
10508     }
10509
10510     // [HGM] PV info: routine tests if comment empty
10511     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10512         DisplayComment(currentMove - 1, commentList[currentMove]);
10513     }
10514     if (!matchMode && appData.timeDelay != 0)
10515       DrawPosition(FALSE, boards[currentMove]);
10516
10517     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10518       programStats.ok_to_send = 1;
10519     }
10520
10521     /* if the first token after the PGN tags is a move
10522      * and not move number 1, retrieve it from the parser
10523      */
10524     if (cm != MoveNumberOne)
10525         LoadGameOneMove(cm);
10526
10527     /* load the remaining moves from the file */
10528     while (LoadGameOneMove(EndOfFile)) {
10529       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10530       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10531     }
10532
10533     /* rewind to the start of the game */
10534     currentMove = backwardMostMove;
10535
10536     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10537
10538     if (oldGameMode == AnalyzeFile ||
10539         oldGameMode == AnalyzeMode) {
10540       AnalyzeFileEvent();
10541     }
10542
10543     if (matchMode || appData.timeDelay == 0) {
10544       ToEndEvent();
10545       gameMode = EditGame;
10546       ModeHighlight();
10547     } else if (appData.timeDelay > 0) {
10548       AutoPlayGameLoop();
10549     }
10550
10551     if (appData.debugMode)
10552         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10553
10554     loadFlag = 0; /* [HGM] true game starts */
10555     return TRUE;
10556 }
10557
10558 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10559 int
10560 ReloadPosition(offset)
10561      int offset;
10562 {
10563     int positionNumber = lastLoadPositionNumber + offset;
10564     if (lastLoadPositionFP == NULL) {
10565         DisplayError(_("No position has been loaded yet"), 0);
10566         return FALSE;
10567     }
10568     if (positionNumber <= 0) {
10569         DisplayError(_("Can't back up any further"), 0);
10570         return FALSE;
10571     }
10572     return LoadPosition(lastLoadPositionFP, positionNumber,
10573                         lastLoadPositionTitle);
10574 }
10575
10576 /* Load the nth position from the given file */
10577 int
10578 LoadPositionFromFile(filename, n, title)
10579      char *filename;
10580      int n;
10581      char *title;
10582 {
10583     FILE *f;
10584     char buf[MSG_SIZ];
10585
10586     if (strcmp(filename, "-") == 0) {
10587         return LoadPosition(stdin, n, "stdin");
10588     } else {
10589         f = fopen(filename, "rb");
10590         if (f == NULL) {
10591             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10592             DisplayError(buf, errno);
10593             return FALSE;
10594         } else {
10595             return LoadPosition(f, n, title);
10596         }
10597     }
10598 }
10599
10600 /* Load the nth position from the given open file, and close it */
10601 int
10602 LoadPosition(f, positionNumber, title)
10603      FILE *f;
10604      int positionNumber;
10605      char *title;
10606 {
10607     char *p, line[MSG_SIZ];
10608     Board initial_position;
10609     int i, j, fenMode, pn;
10610
10611     if (gameMode == Training )
10612         SetTrainingModeOff();
10613
10614     if (gameMode != BeginningOfGame) {
10615         Reset(FALSE, TRUE);
10616     }
10617     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10618         fclose(lastLoadPositionFP);
10619     }
10620     if (positionNumber == 0) positionNumber = 1;
10621     lastLoadPositionFP = f;
10622     lastLoadPositionNumber = positionNumber;
10623     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10624     if (first.pr == NoProc) {
10625       StartChessProgram(&first);
10626       InitChessProgram(&first, FALSE);
10627     }
10628     pn = positionNumber;
10629     if (positionNumber < 0) {
10630         /* Negative position number means to seek to that byte offset */
10631         if (fseek(f, -positionNumber, 0) == -1) {
10632             DisplayError(_("Can't seek on position file"), 0);
10633             return FALSE;
10634         };
10635         pn = 1;
10636     } else {
10637         if (fseek(f, 0, 0) == -1) {
10638             if (f == lastLoadPositionFP ?
10639                 positionNumber == lastLoadPositionNumber + 1 :
10640                 positionNumber == 1) {
10641                 pn = 1;
10642             } else {
10643                 DisplayError(_("Can't seek on position file"), 0);
10644                 return FALSE;
10645             }
10646         }
10647     }
10648     /* See if this file is FEN or old-style xboard */
10649     if (fgets(line, MSG_SIZ, f) == NULL) {
10650         DisplayError(_("Position not found in file"), 0);
10651         return FALSE;
10652     }
10653     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10654     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10655
10656     if (pn >= 2) {
10657         if (fenMode || line[0] == '#') pn--;
10658         while (pn > 0) {
10659             /* skip positions before number pn */
10660             if (fgets(line, MSG_SIZ, f) == NULL) {
10661                 Reset(TRUE, TRUE);
10662                 DisplayError(_("Position not found in file"), 0);
10663                 return FALSE;
10664             }
10665             if (fenMode || line[0] == '#') pn--;
10666         }
10667     }
10668
10669     if (fenMode) {
10670         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10671             DisplayError(_("Bad FEN position in file"), 0);
10672             return FALSE;
10673         }
10674     } else {
10675         (void) fgets(line, MSG_SIZ, f);
10676         (void) fgets(line, MSG_SIZ, f);
10677
10678         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10679             (void) fgets(line, MSG_SIZ, f);
10680             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10681                 if (*p == ' ')
10682                   continue;
10683                 initial_position[i][j++] = CharToPiece(*p);
10684             }
10685         }
10686
10687         blackPlaysFirst = FALSE;
10688         if (!feof(f)) {
10689             (void) fgets(line, MSG_SIZ, f);
10690             if (strncmp(line, "black", strlen("black"))==0)
10691               blackPlaysFirst = TRUE;
10692         }
10693     }
10694     startedFromSetupPosition = TRUE;
10695
10696     SendToProgram("force\n", &first);
10697     CopyBoard(boards[0], initial_position);
10698     if (blackPlaysFirst) {
10699         currentMove = forwardMostMove = backwardMostMove = 1;
10700         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10701         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10702         CopyBoard(boards[1], initial_position);
10703         DisplayMessage("", _("Black to play"));
10704     } else {
10705         currentMove = forwardMostMove = backwardMostMove = 0;
10706         DisplayMessage("", _("White to play"));
10707     }
10708     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10709     SendBoard(&first, forwardMostMove);
10710     if (appData.debugMode) {
10711 int i, j;
10712   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10713   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10714         fprintf(debugFP, "Load Position\n");
10715     }
10716
10717     if (positionNumber > 1) {
10718       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10719         DisplayTitle(line);
10720     } else {
10721         DisplayTitle(title);
10722     }
10723     gameMode = EditGame;
10724     ModeHighlight();
10725     ResetClocks();
10726     timeRemaining[0][1] = whiteTimeRemaining;
10727     timeRemaining[1][1] = blackTimeRemaining;
10728     DrawPosition(FALSE, boards[currentMove]);
10729
10730     return TRUE;
10731 }
10732
10733
10734 void
10735 CopyPlayerNameIntoFileName(dest, src)
10736      char **dest, *src;
10737 {
10738     while (*src != NULLCHAR && *src != ',') {
10739         if (*src == ' ') {
10740             *(*dest)++ = '_';
10741             src++;
10742         } else {
10743             *(*dest)++ = *src++;
10744         }
10745     }
10746 }
10747
10748 char *DefaultFileName(ext)
10749      char *ext;
10750 {
10751     static char def[MSG_SIZ];
10752     char *p;
10753
10754     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10755         p = def;
10756         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10757         *p++ = '-';
10758         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10759         *p++ = '.';
10760         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10761     } else {
10762         def[0] = NULLCHAR;
10763     }
10764     return def;
10765 }
10766
10767 /* Save the current game to the given file */
10768 int
10769 SaveGameToFile(filename, append)
10770      char *filename;
10771      int append;
10772 {
10773     FILE *f;
10774     char buf[MSG_SIZ];
10775
10776     if (strcmp(filename, "-") == 0) {
10777         return SaveGame(stdout, 0, NULL);
10778     } else {
10779         f = fopen(filename, append ? "a" : "w");
10780         if (f == NULL) {
10781             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10782             DisplayError(buf, errno);
10783             return FALSE;
10784         } else {
10785             return SaveGame(f, 0, NULL);
10786         }
10787     }
10788 }
10789
10790 char *
10791 SavePart(str)
10792      char *str;
10793 {
10794     static char buf[MSG_SIZ];
10795     char *p;
10796
10797     p = strchr(str, ' ');
10798     if (p == NULL) return str;
10799     strncpy(buf, str, p - str);
10800     buf[p - str] = NULLCHAR;
10801     return buf;
10802 }
10803
10804 #define PGN_MAX_LINE 75
10805
10806 #define PGN_SIDE_WHITE  0
10807 #define PGN_SIDE_BLACK  1
10808
10809 /* [AS] */
10810 static int FindFirstMoveOutOfBook( int side )
10811 {
10812     int result = -1;
10813
10814     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10815         int index = backwardMostMove;
10816         int has_book_hit = 0;
10817
10818         if( (index % 2) != side ) {
10819             index++;
10820         }
10821
10822         while( index < forwardMostMove ) {
10823             /* Check to see if engine is in book */
10824             int depth = pvInfoList[index].depth;
10825             int score = pvInfoList[index].score;
10826             int in_book = 0;
10827
10828             if( depth <= 2 ) {
10829                 in_book = 1;
10830             }
10831             else if( score == 0 && depth == 63 ) {
10832                 in_book = 1; /* Zappa */
10833             }
10834             else if( score == 2 && depth == 99 ) {
10835                 in_book = 1; /* Abrok */
10836             }
10837
10838             has_book_hit += in_book;
10839
10840             if( ! in_book ) {
10841                 result = index;
10842
10843                 break;
10844             }
10845
10846             index += 2;
10847         }
10848     }
10849
10850     return result;
10851 }
10852
10853 /* [AS] */
10854 void GetOutOfBookInfo( char * buf )
10855 {
10856     int oob[2];
10857     int i;
10858     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10859
10860     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10861     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10862
10863     *buf = '\0';
10864
10865     if( oob[0] >= 0 || oob[1] >= 0 ) {
10866         for( i=0; i<2; i++ ) {
10867             int idx = oob[i];
10868
10869             if( idx >= 0 ) {
10870                 if( i > 0 && oob[0] >= 0 ) {
10871                     strcat( buf, "   " );
10872                 }
10873
10874                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10875                 sprintf( buf+strlen(buf), "%s%.2f",
10876                     pvInfoList[idx].score >= 0 ? "+" : "",
10877                     pvInfoList[idx].score / 100.0 );
10878             }
10879         }
10880     }
10881 }
10882
10883 /* Save game in PGN style and close the file */
10884 int
10885 SaveGamePGN(f)
10886      FILE *f;
10887 {
10888     int i, offset, linelen, newblock;
10889     time_t tm;
10890 //    char *movetext;
10891     char numtext[32];
10892     int movelen, numlen, blank;
10893     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10894
10895     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10896
10897     tm = time((time_t *) NULL);
10898
10899     PrintPGNTags(f, &gameInfo);
10900
10901     if (backwardMostMove > 0 || startedFromSetupPosition) {
10902         char *fen = PositionToFEN(backwardMostMove, NULL);
10903         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10904         fprintf(f, "\n{--------------\n");
10905         PrintPosition(f, backwardMostMove);
10906         fprintf(f, "--------------}\n");
10907         free(fen);
10908     }
10909     else {
10910         /* [AS] Out of book annotation */
10911         if( appData.saveOutOfBookInfo ) {
10912             char buf[64];
10913
10914             GetOutOfBookInfo( buf );
10915
10916             if( buf[0] != '\0' ) {
10917                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10918             }
10919         }
10920
10921         fprintf(f, "\n");
10922     }
10923
10924     i = backwardMostMove;
10925     linelen = 0;
10926     newblock = TRUE;
10927
10928     while (i < forwardMostMove) {
10929         /* Print comments preceding this move */
10930         if (commentList[i] != NULL) {
10931             if (linelen > 0) fprintf(f, "\n");
10932             fprintf(f, "%s", commentList[i]);
10933             linelen = 0;
10934             newblock = TRUE;
10935         }
10936
10937         /* Format move number */
10938         if ((i % 2) == 0)
10939           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10940         else
10941           if (newblock)
10942             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10943           else
10944             numtext[0] = NULLCHAR;
10945
10946         numlen = strlen(numtext);
10947         newblock = FALSE;
10948
10949         /* Print move number */
10950         blank = linelen > 0 && numlen > 0;
10951         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10952             fprintf(f, "\n");
10953             linelen = 0;
10954             blank = 0;
10955         }
10956         if (blank) {
10957             fprintf(f, " ");
10958             linelen++;
10959         }
10960         fprintf(f, "%s", numtext);
10961         linelen += numlen;
10962
10963         /* Get move */
10964         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10965         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10966
10967         /* Print move */
10968         blank = linelen > 0 && movelen > 0;
10969         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10970             fprintf(f, "\n");
10971             linelen = 0;
10972             blank = 0;
10973         }
10974         if (blank) {
10975             fprintf(f, " ");
10976             linelen++;
10977         }
10978         fprintf(f, "%s", move_buffer);
10979         linelen += movelen;
10980
10981         /* [AS] Add PV info if present */
10982         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10983             /* [HGM] add time */
10984             char buf[MSG_SIZ]; int seconds;
10985
10986             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10987
10988             if( seconds <= 0)
10989               buf[0] = 0;
10990             else
10991               if( seconds < 30 )
10992                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10993               else
10994                 {
10995                   seconds = (seconds + 4)/10; // round to full seconds
10996                   if( seconds < 60 )
10997                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10998                   else
10999                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11000                 }
11001
11002             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11003                       pvInfoList[i].score >= 0 ? "+" : "",
11004                       pvInfoList[i].score / 100.0,
11005                       pvInfoList[i].depth,
11006                       buf );
11007
11008             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11009
11010             /* Print score/depth */
11011             blank = linelen > 0 && movelen > 0;
11012             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11013                 fprintf(f, "\n");
11014                 linelen = 0;
11015                 blank = 0;
11016             }
11017             if (blank) {
11018                 fprintf(f, " ");
11019                 linelen++;
11020             }
11021             fprintf(f, "%s", move_buffer);
11022             linelen += movelen;
11023         }
11024
11025         i++;
11026     }
11027
11028     /* Start a new line */
11029     if (linelen > 0) fprintf(f, "\n");
11030
11031     /* Print comments after last move */
11032     if (commentList[i] != NULL) {
11033         fprintf(f, "%s\n", commentList[i]);
11034     }
11035
11036     /* Print result */
11037     if (gameInfo.resultDetails != NULL &&
11038         gameInfo.resultDetails[0] != NULLCHAR) {
11039         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11040                 PGNResult(gameInfo.result));
11041     } else {
11042         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11043     }
11044
11045     fclose(f);
11046     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11047     return TRUE;
11048 }
11049
11050 /* Save game in old style and close the file */
11051 int
11052 SaveGameOldStyle(f)
11053      FILE *f;
11054 {
11055     int i, offset;
11056     time_t tm;
11057
11058     tm = time((time_t *) NULL);
11059
11060     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11061     PrintOpponents(f);
11062
11063     if (backwardMostMove > 0 || startedFromSetupPosition) {
11064         fprintf(f, "\n[--------------\n");
11065         PrintPosition(f, backwardMostMove);
11066         fprintf(f, "--------------]\n");
11067     } else {
11068         fprintf(f, "\n");
11069     }
11070
11071     i = backwardMostMove;
11072     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11073
11074     while (i < forwardMostMove) {
11075         if (commentList[i] != NULL) {
11076             fprintf(f, "[%s]\n", commentList[i]);
11077         }
11078
11079         if ((i % 2) == 1) {
11080             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11081             i++;
11082         } else {
11083             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11084             i++;
11085             if (commentList[i] != NULL) {
11086                 fprintf(f, "\n");
11087                 continue;
11088             }
11089             if (i >= forwardMostMove) {
11090                 fprintf(f, "\n");
11091                 break;
11092             }
11093             fprintf(f, "%s\n", parseList[i]);
11094             i++;
11095         }
11096     }
11097
11098     if (commentList[i] != NULL) {
11099         fprintf(f, "[%s]\n", commentList[i]);
11100     }
11101
11102     /* This isn't really the old style, but it's close enough */
11103     if (gameInfo.resultDetails != NULL &&
11104         gameInfo.resultDetails[0] != NULLCHAR) {
11105         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11106                 gameInfo.resultDetails);
11107     } else {
11108         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11109     }
11110
11111     fclose(f);
11112     return TRUE;
11113 }
11114
11115 /* Save the current game to open file f and close the file */
11116 int
11117 SaveGame(f, dummy, dummy2)
11118      FILE *f;
11119      int dummy;
11120      char *dummy2;
11121 {
11122     if (gameMode == EditPosition) EditPositionDone(TRUE);
11123     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11124     if (appData.oldSaveStyle)
11125       return SaveGameOldStyle(f);
11126     else
11127       return SaveGamePGN(f);
11128 }
11129
11130 /* Save the current position to the given file */
11131 int
11132 SavePositionToFile(filename)
11133      char *filename;
11134 {
11135     FILE *f;
11136     char buf[MSG_SIZ];
11137
11138     if (strcmp(filename, "-") == 0) {
11139         return SavePosition(stdout, 0, NULL);
11140     } else {
11141         f = fopen(filename, "a");
11142         if (f == NULL) {
11143             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11144             DisplayError(buf, errno);
11145             return FALSE;
11146         } else {
11147             SavePosition(f, 0, NULL);
11148             return TRUE;
11149         }
11150     }
11151 }
11152
11153 /* Save the current position to the given open file and close the file */
11154 int
11155 SavePosition(f, dummy, dummy2)
11156      FILE *f;
11157      int dummy;
11158      char *dummy2;
11159 {
11160     time_t tm;
11161     char *fen;
11162
11163     if (gameMode == EditPosition) EditPositionDone(TRUE);
11164     if (appData.oldSaveStyle) {
11165         tm = time((time_t *) NULL);
11166
11167         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11168         PrintOpponents(f);
11169         fprintf(f, "[--------------\n");
11170         PrintPosition(f, currentMove);
11171         fprintf(f, "--------------]\n");
11172     } else {
11173         fen = PositionToFEN(currentMove, NULL);
11174         fprintf(f, "%s\n", fen);
11175         free(fen);
11176     }
11177     fclose(f);
11178     return TRUE;
11179 }
11180
11181 void
11182 ReloadCmailMsgEvent(unregister)
11183      int unregister;
11184 {
11185 #if !WIN32
11186     static char *inFilename = NULL;
11187     static char *outFilename;
11188     int i;
11189     struct stat inbuf, outbuf;
11190     int status;
11191
11192     /* Any registered moves are unregistered if unregister is set, */
11193     /* i.e. invoked by the signal handler */
11194     if (unregister) {
11195         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11196             cmailMoveRegistered[i] = FALSE;
11197             if (cmailCommentList[i] != NULL) {
11198                 free(cmailCommentList[i]);
11199                 cmailCommentList[i] = NULL;
11200             }
11201         }
11202         nCmailMovesRegistered = 0;
11203     }
11204
11205     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11206         cmailResult[i] = CMAIL_NOT_RESULT;
11207     }
11208     nCmailResults = 0;
11209
11210     if (inFilename == NULL) {
11211         /* Because the filenames are static they only get malloced once  */
11212         /* and they never get freed                                      */
11213         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11214         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11215
11216         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11217         sprintf(outFilename, "%s.out", appData.cmailGameName);
11218     }
11219
11220     status = stat(outFilename, &outbuf);
11221     if (status < 0) {
11222         cmailMailedMove = FALSE;
11223     } else {
11224         status = stat(inFilename, &inbuf);
11225         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11226     }
11227
11228     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11229        counts the games, notes how each one terminated, etc.
11230
11231        It would be nice to remove this kludge and instead gather all
11232        the information while building the game list.  (And to keep it
11233        in the game list nodes instead of having a bunch of fixed-size
11234        parallel arrays.)  Note this will require getting each game's
11235        termination from the PGN tags, as the game list builder does
11236        not process the game moves.  --mann
11237        */
11238     cmailMsgLoaded = TRUE;
11239     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11240
11241     /* Load first game in the file or popup game menu */
11242     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11243
11244 #endif /* !WIN32 */
11245     return;
11246 }
11247
11248 int
11249 RegisterMove()
11250 {
11251     FILE *f;
11252     char string[MSG_SIZ];
11253
11254     if (   cmailMailedMove
11255         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11256         return TRUE;            /* Allow free viewing  */
11257     }
11258
11259     /* Unregister move to ensure that we don't leave RegisterMove        */
11260     /* with the move registered when the conditions for registering no   */
11261     /* longer hold                                                       */
11262     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11263         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11264         nCmailMovesRegistered --;
11265
11266         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11267           {
11268               free(cmailCommentList[lastLoadGameNumber - 1]);
11269               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11270           }
11271     }
11272
11273     if (cmailOldMove == -1) {
11274         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11275         return FALSE;
11276     }
11277
11278     if (currentMove > cmailOldMove + 1) {
11279         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11280         return FALSE;
11281     }
11282
11283     if (currentMove < cmailOldMove) {
11284         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11285         return FALSE;
11286     }
11287
11288     if (forwardMostMove > currentMove) {
11289         /* Silently truncate extra moves */
11290         TruncateGame();
11291     }
11292
11293     if (   (currentMove == cmailOldMove + 1)
11294         || (   (currentMove == cmailOldMove)
11295             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11296                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11297         if (gameInfo.result != GameUnfinished) {
11298             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11299         }
11300
11301         if (commentList[currentMove] != NULL) {
11302             cmailCommentList[lastLoadGameNumber - 1]
11303               = StrSave(commentList[currentMove]);
11304         }
11305         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11306
11307         if (appData.debugMode)
11308           fprintf(debugFP, "Saving %s for game %d\n",
11309                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11310
11311         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11312
11313         f = fopen(string, "w");
11314         if (appData.oldSaveStyle) {
11315             SaveGameOldStyle(f); /* also closes the file */
11316
11317             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11318             f = fopen(string, "w");
11319             SavePosition(f, 0, NULL); /* also closes the file */
11320         } else {
11321             fprintf(f, "{--------------\n");
11322             PrintPosition(f, currentMove);
11323             fprintf(f, "--------------}\n\n");
11324
11325             SaveGame(f, 0, NULL); /* also closes the file*/
11326         }
11327
11328         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11329         nCmailMovesRegistered ++;
11330     } else if (nCmailGames == 1) {
11331         DisplayError(_("You have not made a move yet"), 0);
11332         return FALSE;
11333     }
11334
11335     return TRUE;
11336 }
11337
11338 void
11339 MailMoveEvent()
11340 {
11341 #if !WIN32
11342     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11343     FILE *commandOutput;
11344     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11345     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11346     int nBuffers;
11347     int i;
11348     int archived;
11349     char *arcDir;
11350
11351     if (! cmailMsgLoaded) {
11352         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11353         return;
11354     }
11355
11356     if (nCmailGames == nCmailResults) {
11357         DisplayError(_("No unfinished games"), 0);
11358         return;
11359     }
11360
11361 #if CMAIL_PROHIBIT_REMAIL
11362     if (cmailMailedMove) {
11363       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);
11364         DisplayError(msg, 0);
11365         return;
11366     }
11367 #endif
11368
11369     if (! (cmailMailedMove || RegisterMove())) return;
11370
11371     if (   cmailMailedMove
11372         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11373       snprintf(string, MSG_SIZ, partCommandString,
11374                appData.debugMode ? " -v" : "", appData.cmailGameName);
11375         commandOutput = popen(string, "r");
11376
11377         if (commandOutput == NULL) {
11378             DisplayError(_("Failed to invoke cmail"), 0);
11379         } else {
11380             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11381                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11382             }
11383             if (nBuffers > 1) {
11384                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11385                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11386                 nBytes = MSG_SIZ - 1;
11387             } else {
11388                 (void) memcpy(msg, buffer, nBytes);
11389             }
11390             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11391
11392             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11393                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11394
11395                 archived = TRUE;
11396                 for (i = 0; i < nCmailGames; i ++) {
11397                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11398                         archived = FALSE;
11399                     }
11400                 }
11401                 if (   archived
11402                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11403                         != NULL)) {
11404                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11405                            arcDir,
11406                            appData.cmailGameName,
11407                            gameInfo.date);
11408                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11409                     cmailMsgLoaded = FALSE;
11410                 }
11411             }
11412
11413             DisplayInformation(msg);
11414             pclose(commandOutput);
11415         }
11416     } else {
11417         if ((*cmailMsg) != '\0') {
11418             DisplayInformation(cmailMsg);
11419         }
11420     }
11421
11422     return;
11423 #endif /* !WIN32 */
11424 }
11425
11426 char *
11427 CmailMsg()
11428 {
11429 #if WIN32
11430     return NULL;
11431 #else
11432     int  prependComma = 0;
11433     char number[5];
11434     char string[MSG_SIZ];       /* Space for game-list */
11435     int  i;
11436
11437     if (!cmailMsgLoaded) return "";
11438
11439     if (cmailMailedMove) {
11440       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11441     } else {
11442         /* Create a list of games left */
11443       snprintf(string, MSG_SIZ, "[");
11444         for (i = 0; i < nCmailGames; i ++) {
11445             if (! (   cmailMoveRegistered[i]
11446                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11447                 if (prependComma) {
11448                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11449                 } else {
11450                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11451                     prependComma = 1;
11452                 }
11453
11454                 strcat(string, number);
11455             }
11456         }
11457         strcat(string, "]");
11458
11459         if (nCmailMovesRegistered + nCmailResults == 0) {
11460             switch (nCmailGames) {
11461               case 1:
11462                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11463                 break;
11464
11465               case 2:
11466                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11467                 break;
11468
11469               default:
11470                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11471                          nCmailGames);
11472                 break;
11473             }
11474         } else {
11475             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11476               case 1:
11477                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11478                          string);
11479                 break;
11480
11481               case 0:
11482                 if (nCmailResults == nCmailGames) {
11483                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11484                 } else {
11485                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11486                 }
11487                 break;
11488
11489               default:
11490                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11491                          string);
11492             }
11493         }
11494     }
11495     return cmailMsg;
11496 #endif /* WIN32 */
11497 }
11498
11499 void
11500 ResetGameEvent()
11501 {
11502     if (gameMode == Training)
11503       SetTrainingModeOff();
11504
11505     Reset(TRUE, TRUE);
11506     cmailMsgLoaded = FALSE;
11507     if (appData.icsActive) {
11508       SendToICS(ics_prefix);
11509       SendToICS("refresh\n");
11510     }
11511 }
11512
11513 void
11514 ExitEvent(status)
11515      int status;
11516 {
11517     exiting++;
11518     if (exiting > 2) {
11519       /* Give up on clean exit */
11520       exit(status);
11521     }
11522     if (exiting > 1) {
11523       /* Keep trying for clean exit */
11524       return;
11525     }
11526
11527     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11528
11529     if (telnetISR != NULL) {
11530       RemoveInputSource(telnetISR);
11531     }
11532     if (icsPR != NoProc) {
11533       DestroyChildProcess(icsPR, TRUE);
11534     }
11535
11536     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11537     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11538
11539     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11540     /* make sure this other one finishes before killing it!                  */
11541     if(endingGame) { int count = 0;
11542         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11543         while(endingGame && count++ < 10) DoSleep(1);
11544         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11545     }
11546
11547     /* Kill off chess programs */
11548     if (first.pr != NoProc) {
11549         ExitAnalyzeMode();
11550
11551         DoSleep( appData.delayBeforeQuit );
11552         SendToProgram("quit\n", &first);
11553         DoSleep( appData.delayAfterQuit );
11554         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11555     }
11556     if (second.pr != NoProc) {
11557         DoSleep( appData.delayBeforeQuit );
11558         SendToProgram("quit\n", &second);
11559         DoSleep( appData.delayAfterQuit );
11560         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11561     }
11562     if (first.isr != NULL) {
11563         RemoveInputSource(first.isr);
11564     }
11565     if (second.isr != NULL) {
11566         RemoveInputSource(second.isr);
11567     }
11568
11569     ShutDownFrontEnd();
11570     exit(status);
11571 }
11572
11573 void
11574 PauseEvent()
11575 {
11576     if (appData.debugMode)
11577         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11578     if (pausing) {
11579         pausing = FALSE;
11580         ModeHighlight();
11581         if (gameMode == MachinePlaysWhite ||
11582             gameMode == MachinePlaysBlack) {
11583             StartClocks();
11584         } else {
11585             DisplayBothClocks();
11586         }
11587         if (gameMode == PlayFromGameFile) {
11588             if (appData.timeDelay >= 0)
11589                 AutoPlayGameLoop();
11590         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11591             Reset(FALSE, TRUE);
11592             SendToICS(ics_prefix);
11593             SendToICS("refresh\n");
11594         } else if (currentMove < forwardMostMove) {
11595             ForwardInner(forwardMostMove);
11596         }
11597         pauseExamInvalid = FALSE;
11598     } else {
11599         switch (gameMode) {
11600           default:
11601             return;
11602           case IcsExamining:
11603             pauseExamForwardMostMove = forwardMostMove;
11604             pauseExamInvalid = FALSE;
11605             /* fall through */
11606           case IcsObserving:
11607           case IcsPlayingWhite:
11608           case IcsPlayingBlack:
11609             pausing = TRUE;
11610             ModeHighlight();
11611             return;
11612           case PlayFromGameFile:
11613             (void) StopLoadGameTimer();
11614             pausing = TRUE;
11615             ModeHighlight();
11616             break;
11617           case BeginningOfGame:
11618             if (appData.icsActive) return;
11619             /* else fall through */
11620           case MachinePlaysWhite:
11621           case MachinePlaysBlack:
11622           case TwoMachinesPlay:
11623             if (forwardMostMove == 0)
11624               return;           /* don't pause if no one has moved */
11625             if ((gameMode == MachinePlaysWhite &&
11626                  !WhiteOnMove(forwardMostMove)) ||
11627                 (gameMode == MachinePlaysBlack &&
11628                  WhiteOnMove(forwardMostMove))) {
11629                 StopClocks();
11630             }
11631             pausing = TRUE;
11632             ModeHighlight();
11633             break;
11634         }
11635     }
11636 }
11637
11638 void
11639 EditCommentEvent()
11640 {
11641     char title[MSG_SIZ];
11642
11643     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11644       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11645     } else {
11646       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11647                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11648                parseList[currentMove - 1]);
11649     }
11650
11651     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11652 }
11653
11654
11655 void
11656 EditTagsEvent()
11657 {
11658     char *tags = PGNTags(&gameInfo);
11659     EditTagsPopUp(tags, NULL);
11660     free(tags);
11661 }
11662
11663 void
11664 AnalyzeModeEvent()
11665 {
11666     if (appData.noChessProgram || gameMode == AnalyzeMode)
11667       return;
11668
11669     if (gameMode != AnalyzeFile) {
11670         if (!appData.icsEngineAnalyze) {
11671                EditGameEvent();
11672                if (gameMode != EditGame) return;
11673         }
11674         ResurrectChessProgram();
11675         SendToProgram("analyze\n", &first);
11676         first.analyzing = TRUE;
11677         /*first.maybeThinking = TRUE;*/
11678         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11679         EngineOutputPopUp();
11680     }
11681     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11682     pausing = FALSE;
11683     ModeHighlight();
11684     SetGameInfo();
11685
11686     StartAnalysisClock();
11687     GetTimeMark(&lastNodeCountTime);
11688     lastNodeCount = 0;
11689 }
11690
11691 void
11692 AnalyzeFileEvent()
11693 {
11694     if (appData.noChessProgram || gameMode == AnalyzeFile)
11695       return;
11696
11697     if (gameMode != AnalyzeMode) {
11698         EditGameEvent();
11699         if (gameMode != EditGame) return;
11700         ResurrectChessProgram();
11701         SendToProgram("analyze\n", &first);
11702         first.analyzing = TRUE;
11703         /*first.maybeThinking = TRUE;*/
11704         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11705         EngineOutputPopUp();
11706     }
11707     gameMode = AnalyzeFile;
11708     pausing = FALSE;
11709     ModeHighlight();
11710     SetGameInfo();
11711
11712     StartAnalysisClock();
11713     GetTimeMark(&lastNodeCountTime);
11714     lastNodeCount = 0;
11715 }
11716
11717 void
11718 MachineWhiteEvent()
11719 {
11720     char buf[MSG_SIZ];
11721     char *bookHit = NULL;
11722
11723     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11724       return;
11725
11726
11727     if (gameMode == PlayFromGameFile ||
11728         gameMode == TwoMachinesPlay  ||
11729         gameMode == Training         ||
11730         gameMode == AnalyzeMode      ||
11731         gameMode == EndOfGame)
11732         EditGameEvent();
11733
11734     if (gameMode == EditPosition)
11735         EditPositionDone(TRUE);
11736
11737     if (!WhiteOnMove(currentMove)) {
11738         DisplayError(_("It is not White's turn"), 0);
11739         return;
11740     }
11741
11742     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11743       ExitAnalyzeMode();
11744
11745     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11746         gameMode == AnalyzeFile)
11747         TruncateGame();
11748
11749     ResurrectChessProgram();    /* in case it isn't running */
11750     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11751         gameMode = MachinePlaysWhite;
11752         ResetClocks();
11753     } else
11754     gameMode = MachinePlaysWhite;
11755     pausing = FALSE;
11756     ModeHighlight();
11757     SetGameInfo();
11758     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11759     DisplayTitle(buf);
11760     if (first.sendName) {
11761       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11762       SendToProgram(buf, &first);
11763     }
11764     if (first.sendTime) {
11765       if (first.useColors) {
11766         SendToProgram("black\n", &first); /*gnu kludge*/
11767       }
11768       SendTimeRemaining(&first, TRUE);
11769     }
11770     if (first.useColors) {
11771       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11772     }
11773     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11774     SetMachineThinkingEnables();
11775     first.maybeThinking = TRUE;
11776     StartClocks();
11777     firstMove = FALSE;
11778
11779     if (appData.autoFlipView && !flipView) {
11780       flipView = !flipView;
11781       DrawPosition(FALSE, NULL);
11782       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11783     }
11784
11785     if(bookHit) { // [HGM] book: simulate book reply
11786         static char bookMove[MSG_SIZ]; // a bit generous?
11787
11788         programStats.nodes = programStats.depth = programStats.time =
11789         programStats.score = programStats.got_only_move = 0;
11790         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11791
11792         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11793         strcat(bookMove, bookHit);
11794         HandleMachineMove(bookMove, &first);
11795     }
11796 }
11797
11798 void
11799 MachineBlackEvent()
11800 {
11801   char buf[MSG_SIZ];
11802   char *bookHit = NULL;
11803
11804     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11805         return;
11806
11807
11808     if (gameMode == PlayFromGameFile ||
11809         gameMode == TwoMachinesPlay  ||
11810         gameMode == Training         ||
11811         gameMode == AnalyzeMode      ||
11812         gameMode == EndOfGame)
11813         EditGameEvent();
11814
11815     if (gameMode == EditPosition)
11816         EditPositionDone(TRUE);
11817
11818     if (WhiteOnMove(currentMove)) {
11819         DisplayError(_("It is not Black's turn"), 0);
11820         return;
11821     }
11822
11823     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11824       ExitAnalyzeMode();
11825
11826     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11827         gameMode == AnalyzeFile)
11828         TruncateGame();
11829
11830     ResurrectChessProgram();    /* in case it isn't running */
11831     gameMode = MachinePlaysBlack;
11832     pausing = FALSE;
11833     ModeHighlight();
11834     SetGameInfo();
11835     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11836     DisplayTitle(buf);
11837     if (first.sendName) {
11838       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11839       SendToProgram(buf, &first);
11840     }
11841     if (first.sendTime) {
11842       if (first.useColors) {
11843         SendToProgram("white\n", &first); /*gnu kludge*/
11844       }
11845       SendTimeRemaining(&first, FALSE);
11846     }
11847     if (first.useColors) {
11848       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11849     }
11850     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11851     SetMachineThinkingEnables();
11852     first.maybeThinking = TRUE;
11853     StartClocks();
11854
11855     if (appData.autoFlipView && flipView) {
11856       flipView = !flipView;
11857       DrawPosition(FALSE, NULL);
11858       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11859     }
11860     if(bookHit) { // [HGM] book: simulate book reply
11861         static char bookMove[MSG_SIZ]; // a bit generous?
11862
11863         programStats.nodes = programStats.depth = programStats.time =
11864         programStats.score = programStats.got_only_move = 0;
11865         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11866
11867         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11868         strcat(bookMove, bookHit);
11869         HandleMachineMove(bookMove, &first);
11870     }
11871 }
11872
11873
11874 void
11875 DisplayTwoMachinesTitle()
11876 {
11877     char buf[MSG_SIZ];
11878     if (appData.matchGames > 0) {
11879         if (first.twoMachinesColor[0] == 'w') {
11880           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11881                    gameInfo.white, gameInfo.black,
11882                    first.matchWins, second.matchWins,
11883                    matchGame - 1 - (first.matchWins + second.matchWins));
11884         } else {
11885           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11886                    gameInfo.white, gameInfo.black,
11887                    second.matchWins, first.matchWins,
11888                    matchGame - 1 - (first.matchWins + second.matchWins));
11889         }
11890     } else {
11891       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11892     }
11893     DisplayTitle(buf);
11894 }
11895
11896 void
11897 SettingsMenuIfReady()
11898 {
11899   if (second.lastPing != second.lastPong) {
11900     DisplayMessage("", _("Waiting for second chess program"));
11901     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11902     return;
11903   }
11904   ThawUI();
11905   DisplayMessage("", "");
11906   SettingsPopUp(&second);
11907 }
11908
11909 int
11910 WaitForSecond(DelayedEventCallback retry)
11911 {
11912     if (second.pr == NULL) {
11913         StartChessProgram(&second);
11914         if (second.protocolVersion == 1) {
11915           retry();
11916         } else {
11917           /* kludge: allow timeout for initial "feature" command */
11918           FreezeUI();
11919           DisplayMessage("", _("Starting second chess program"));
11920           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11921         }
11922         return 1;
11923     }
11924     return 0;
11925 }
11926
11927 void
11928 TwoMachinesEvent P((void))
11929 {
11930     int i;
11931     char buf[MSG_SIZ];
11932     ChessProgramState *onmove;
11933     char *bookHit = NULL;
11934
11935     if (appData.noChessProgram) return;
11936
11937     switch (gameMode) {
11938       case TwoMachinesPlay:
11939         return;
11940       case MachinePlaysWhite:
11941       case MachinePlaysBlack:
11942         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11943             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11944             return;
11945         }
11946         /* fall through */
11947       case BeginningOfGame:
11948       case PlayFromGameFile:
11949       case EndOfGame:
11950         EditGameEvent();
11951         if (gameMode != EditGame) return;
11952         break;
11953       case EditPosition:
11954         EditPositionDone(TRUE);
11955         break;
11956       case AnalyzeMode:
11957       case AnalyzeFile:
11958         ExitAnalyzeMode();
11959         break;
11960       case EditGame:
11961       default:
11962         break;
11963     }
11964
11965 //    forwardMostMove = currentMove;
11966     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11967     ResurrectChessProgram();    /* in case first program isn't running */
11968
11969     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11970     DisplayMessage("", "");
11971     InitChessProgram(&second, FALSE);
11972     SendToProgram("force\n", &second);
11973     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11974       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11975       return;
11976     }
11977     if (startedFromSetupPosition) {
11978         SendBoard(&second, backwardMostMove);
11979     if (appData.debugMode) {
11980         fprintf(debugFP, "Two Machines\n");
11981     }
11982     }
11983     for (i = backwardMostMove; i < forwardMostMove; i++) {
11984         SendMoveToProgram(i, &second);
11985     }
11986
11987     gameMode = TwoMachinesPlay;
11988     pausing = FALSE;
11989     ModeHighlight();
11990     SetGameInfo();
11991     DisplayTwoMachinesTitle();
11992     firstMove = TRUE;
11993     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11994         onmove = &first;
11995     } else {
11996         onmove = &second;
11997     }
11998
11999     SendToProgram(first.computerString, &first);
12000     if (first.sendName) {
12001       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12002       SendToProgram(buf, &first);
12003     }
12004     SendToProgram(second.computerString, &second);
12005     if (second.sendName) {
12006       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12007       SendToProgram(buf, &second);
12008     }
12009
12010     ResetClocks();
12011     if (!first.sendTime || !second.sendTime) {
12012         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12013         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12014     }
12015     if (onmove->sendTime) {
12016       if (onmove->useColors) {
12017         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12018       }
12019       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12020     }
12021     if (onmove->useColors) {
12022       SendToProgram(onmove->twoMachinesColor, onmove);
12023     }
12024     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12025 //    SendToProgram("go\n", onmove);
12026     onmove->maybeThinking = TRUE;
12027     SetMachineThinkingEnables();
12028
12029     StartClocks();
12030
12031     if(bookHit) { // [HGM] book: simulate book reply
12032         static char bookMove[MSG_SIZ]; // a bit generous?
12033
12034         programStats.nodes = programStats.depth = programStats.time =
12035         programStats.score = programStats.got_only_move = 0;
12036         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12037
12038         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12039         strcat(bookMove, bookHit);
12040         savedMessage = bookMove; // args for deferred call
12041         savedState = onmove;
12042         ScheduleDelayedEvent(DeferredBookMove, 1);
12043     }
12044 }
12045
12046 void
12047 TrainingEvent()
12048 {
12049     if (gameMode == Training) {
12050       SetTrainingModeOff();
12051       gameMode = PlayFromGameFile;
12052       DisplayMessage("", _("Training mode off"));
12053     } else {
12054       gameMode = Training;
12055       animateTraining = appData.animate;
12056
12057       /* make sure we are not already at the end of the game */
12058       if (currentMove < forwardMostMove) {
12059         SetTrainingModeOn();
12060         DisplayMessage("", _("Training mode on"));
12061       } else {
12062         gameMode = PlayFromGameFile;
12063         DisplayError(_("Already at end of game"), 0);
12064       }
12065     }
12066     ModeHighlight();
12067 }
12068
12069 void
12070 IcsClientEvent()
12071 {
12072     if (!appData.icsActive) return;
12073     switch (gameMode) {
12074       case IcsPlayingWhite:
12075       case IcsPlayingBlack:
12076       case IcsObserving:
12077       case IcsIdle:
12078       case BeginningOfGame:
12079       case IcsExamining:
12080         return;
12081
12082       case EditGame:
12083         break;
12084
12085       case EditPosition:
12086         EditPositionDone(TRUE);
12087         break;
12088
12089       case AnalyzeMode:
12090       case AnalyzeFile:
12091         ExitAnalyzeMode();
12092         break;
12093
12094       default:
12095         EditGameEvent();
12096         break;
12097     }
12098
12099     gameMode = IcsIdle;
12100     ModeHighlight();
12101     return;
12102 }
12103
12104
12105 void
12106 EditGameEvent()
12107 {
12108     int i;
12109
12110     switch (gameMode) {
12111       case Training:
12112         SetTrainingModeOff();
12113         break;
12114       case MachinePlaysWhite:
12115       case MachinePlaysBlack:
12116       case BeginningOfGame:
12117         SendToProgram("force\n", &first);
12118         SetUserThinkingEnables();
12119         break;
12120       case PlayFromGameFile:
12121         (void) StopLoadGameTimer();
12122         if (gameFileFP != NULL) {
12123             gameFileFP = NULL;
12124         }
12125         break;
12126       case EditPosition:
12127         EditPositionDone(TRUE);
12128         break;
12129       case AnalyzeMode:
12130       case AnalyzeFile:
12131         ExitAnalyzeMode();
12132         SendToProgram("force\n", &first);
12133         break;
12134       case TwoMachinesPlay:
12135         GameEnds(EndOfFile, NULL, GE_PLAYER);
12136         ResurrectChessProgram();
12137         SetUserThinkingEnables();
12138         break;
12139       case EndOfGame:
12140         ResurrectChessProgram();
12141         break;
12142       case IcsPlayingBlack:
12143       case IcsPlayingWhite:
12144         DisplayError(_("Warning: You are still playing a game"), 0);
12145         break;
12146       case IcsObserving:
12147         DisplayError(_("Warning: You are still observing a game"), 0);
12148         break;
12149       case IcsExamining:
12150         DisplayError(_("Warning: You are still examining a game"), 0);
12151         break;
12152       case IcsIdle:
12153         break;
12154       case EditGame:
12155       default:
12156         return;
12157     }
12158
12159     pausing = FALSE;
12160     StopClocks();
12161     first.offeredDraw = second.offeredDraw = 0;
12162
12163     if (gameMode == PlayFromGameFile) {
12164         whiteTimeRemaining = timeRemaining[0][currentMove];
12165         blackTimeRemaining = timeRemaining[1][currentMove];
12166         DisplayTitle("");
12167     }
12168
12169     if (gameMode == MachinePlaysWhite ||
12170         gameMode == MachinePlaysBlack ||
12171         gameMode == TwoMachinesPlay ||
12172         gameMode == EndOfGame) {
12173         i = forwardMostMove;
12174         while (i > currentMove) {
12175             SendToProgram("undo\n", &first);
12176             i--;
12177         }
12178         whiteTimeRemaining = timeRemaining[0][currentMove];
12179         blackTimeRemaining = timeRemaining[1][currentMove];
12180         DisplayBothClocks();
12181         if (whiteFlag || blackFlag) {
12182             whiteFlag = blackFlag = 0;
12183         }
12184         DisplayTitle("");
12185     }
12186
12187     gameMode = EditGame;
12188     ModeHighlight();
12189     SetGameInfo();
12190 }
12191
12192
12193 void
12194 EditPositionEvent()
12195 {
12196     if (gameMode == EditPosition) {
12197         EditGameEvent();
12198         return;
12199     }
12200
12201     EditGameEvent();
12202     if (gameMode != EditGame) return;
12203
12204     gameMode = EditPosition;
12205     ModeHighlight();
12206     SetGameInfo();
12207     if (currentMove > 0)
12208       CopyBoard(boards[0], boards[currentMove]);
12209
12210     blackPlaysFirst = !WhiteOnMove(currentMove);
12211     ResetClocks();
12212     currentMove = forwardMostMove = backwardMostMove = 0;
12213     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12214     DisplayMove(-1);
12215 }
12216
12217 void
12218 ExitAnalyzeMode()
12219 {
12220     /* [DM] icsEngineAnalyze - possible call from other functions */
12221     if (appData.icsEngineAnalyze) {
12222         appData.icsEngineAnalyze = FALSE;
12223
12224         DisplayMessage("",_("Close ICS engine analyze..."));
12225     }
12226     if (first.analysisSupport && first.analyzing) {
12227       SendToProgram("exit\n", &first);
12228       first.analyzing = FALSE;
12229     }
12230     thinkOutput[0] = NULLCHAR;
12231 }
12232
12233 void
12234 EditPositionDone(Boolean fakeRights)
12235 {
12236     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12237
12238     startedFromSetupPosition = TRUE;
12239     InitChessProgram(&first, FALSE);
12240     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12241       boards[0][EP_STATUS] = EP_NONE;
12242       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12243     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12244         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12245         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12246       } else boards[0][CASTLING][2] = NoRights;
12247     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12248         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12249         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12250       } else boards[0][CASTLING][5] = NoRights;
12251     }
12252     SendToProgram("force\n", &first);
12253     if (blackPlaysFirst) {
12254         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12255         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12256         currentMove = forwardMostMove = backwardMostMove = 1;
12257         CopyBoard(boards[1], boards[0]);
12258     } else {
12259         currentMove = forwardMostMove = backwardMostMove = 0;
12260     }
12261     SendBoard(&first, forwardMostMove);
12262     if (appData.debugMode) {
12263         fprintf(debugFP, "EditPosDone\n");
12264     }
12265     DisplayTitle("");
12266     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12267     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12268     gameMode = EditGame;
12269     ModeHighlight();
12270     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12271     ClearHighlights(); /* [AS] */
12272 }
12273
12274 /* Pause for `ms' milliseconds */
12275 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12276 void
12277 TimeDelay(ms)
12278      long ms;
12279 {
12280     TimeMark m1, m2;
12281
12282     GetTimeMark(&m1);
12283     do {
12284         GetTimeMark(&m2);
12285     } while (SubtractTimeMarks(&m2, &m1) < ms);
12286 }
12287
12288 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12289 void
12290 SendMultiLineToICS(buf)
12291      char *buf;
12292 {
12293     char temp[MSG_SIZ+1], *p;
12294     int len;
12295
12296     len = strlen(buf);
12297     if (len > MSG_SIZ)
12298       len = MSG_SIZ;
12299
12300     strncpy(temp, buf, len);
12301     temp[len] = 0;
12302
12303     p = temp;
12304     while (*p) {
12305         if (*p == '\n' || *p == '\r')
12306           *p = ' ';
12307         ++p;
12308     }
12309
12310     strcat(temp, "\n");
12311     SendToICS(temp);
12312     SendToPlayer(temp, strlen(temp));
12313 }
12314
12315 void
12316 SetWhiteToPlayEvent()
12317 {
12318     if (gameMode == EditPosition) {
12319         blackPlaysFirst = FALSE;
12320         DisplayBothClocks();    /* works because currentMove is 0 */
12321     } else if (gameMode == IcsExamining) {
12322         SendToICS(ics_prefix);
12323         SendToICS("tomove white\n");
12324     }
12325 }
12326
12327 void
12328 SetBlackToPlayEvent()
12329 {
12330     if (gameMode == EditPosition) {
12331         blackPlaysFirst = TRUE;
12332         currentMove = 1;        /* kludge */
12333         DisplayBothClocks();
12334         currentMove = 0;
12335     } else if (gameMode == IcsExamining) {
12336         SendToICS(ics_prefix);
12337         SendToICS("tomove black\n");
12338     }
12339 }
12340
12341 void
12342 EditPositionMenuEvent(selection, x, y)
12343      ChessSquare selection;
12344      int x, y;
12345 {
12346     char buf[MSG_SIZ];
12347     ChessSquare piece = boards[0][y][x];
12348
12349     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12350
12351     switch (selection) {
12352       case ClearBoard:
12353         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12354             SendToICS(ics_prefix);
12355             SendToICS("bsetup clear\n");
12356         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12357             SendToICS(ics_prefix);
12358             SendToICS("clearboard\n");
12359         } else {
12360             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12361                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12362                 for (y = 0; y < BOARD_HEIGHT; y++) {
12363                     if (gameMode == IcsExamining) {
12364                         if (boards[currentMove][y][x] != EmptySquare) {
12365                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12366                                     AAA + x, ONE + y);
12367                             SendToICS(buf);
12368                         }
12369                     } else {
12370                         boards[0][y][x] = p;
12371                     }
12372                 }
12373             }
12374         }
12375         if (gameMode == EditPosition) {
12376             DrawPosition(FALSE, boards[0]);
12377         }
12378         break;
12379
12380       case WhitePlay:
12381         SetWhiteToPlayEvent();
12382         break;
12383
12384       case BlackPlay:
12385         SetBlackToPlayEvent();
12386         break;
12387
12388       case EmptySquare:
12389         if (gameMode == IcsExamining) {
12390             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12391             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12392             SendToICS(buf);
12393         } else {
12394             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12395                 if(x == BOARD_LEFT-2) {
12396                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12397                     boards[0][y][1] = 0;
12398                 } else
12399                 if(x == BOARD_RGHT+1) {
12400                     if(y >= gameInfo.holdingsSize) break;
12401                     boards[0][y][BOARD_WIDTH-2] = 0;
12402                 } else break;
12403             }
12404             boards[0][y][x] = EmptySquare;
12405             DrawPosition(FALSE, boards[0]);
12406         }
12407         break;
12408
12409       case PromotePiece:
12410         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12411            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12412             selection = (ChessSquare) (PROMOTED piece);
12413         } else if(piece == EmptySquare) selection = WhiteSilver;
12414         else selection = (ChessSquare)((int)piece - 1);
12415         goto defaultlabel;
12416
12417       case DemotePiece:
12418         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12419            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12420             selection = (ChessSquare) (DEMOTED piece);
12421         } else if(piece == EmptySquare) selection = BlackSilver;
12422         else selection = (ChessSquare)((int)piece + 1);
12423         goto defaultlabel;
12424
12425       case WhiteQueen:
12426       case BlackQueen:
12427         if(gameInfo.variant == VariantShatranj ||
12428            gameInfo.variant == VariantXiangqi  ||
12429            gameInfo.variant == VariantCourier  ||
12430            gameInfo.variant == VariantMakruk     )
12431             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12432         goto defaultlabel;
12433
12434       case WhiteKing:
12435       case BlackKing:
12436         if(gameInfo.variant == VariantXiangqi)
12437             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12438         if(gameInfo.variant == VariantKnightmate)
12439             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12440       default:
12441         defaultlabel:
12442         if (gameMode == IcsExamining) {
12443             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12444             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12445                      PieceToChar(selection), AAA + x, ONE + y);
12446             SendToICS(buf);
12447         } else {
12448             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12449                 int n;
12450                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12451                     n = PieceToNumber(selection - BlackPawn);
12452                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12453                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12454                     boards[0][BOARD_HEIGHT-1-n][1]++;
12455                 } else
12456                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12457                     n = PieceToNumber(selection);
12458                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12459                     boards[0][n][BOARD_WIDTH-1] = selection;
12460                     boards[0][n][BOARD_WIDTH-2]++;
12461                 }
12462             } else
12463             boards[0][y][x] = selection;
12464             DrawPosition(TRUE, boards[0]);
12465         }
12466         break;
12467     }
12468 }
12469
12470
12471 void
12472 DropMenuEvent(selection, x, y)
12473      ChessSquare selection;
12474      int x, y;
12475 {
12476     ChessMove moveType;
12477
12478     switch (gameMode) {
12479       case IcsPlayingWhite:
12480       case MachinePlaysBlack:
12481         if (!WhiteOnMove(currentMove)) {
12482             DisplayMoveError(_("It is Black's turn"));
12483             return;
12484         }
12485         moveType = WhiteDrop;
12486         break;
12487       case IcsPlayingBlack:
12488       case MachinePlaysWhite:
12489         if (WhiteOnMove(currentMove)) {
12490             DisplayMoveError(_("It is White's turn"));
12491             return;
12492         }
12493         moveType = BlackDrop;
12494         break;
12495       case EditGame:
12496         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12497         break;
12498       default:
12499         return;
12500     }
12501
12502     if (moveType == BlackDrop && selection < BlackPawn) {
12503       selection = (ChessSquare) ((int) selection
12504                                  + (int) BlackPawn - (int) WhitePawn);
12505     }
12506     if (boards[currentMove][y][x] != EmptySquare) {
12507         DisplayMoveError(_("That square is occupied"));
12508         return;
12509     }
12510
12511     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12512 }
12513
12514 void
12515 AcceptEvent()
12516 {
12517     /* Accept a pending offer of any kind from opponent */
12518
12519     if (appData.icsActive) {
12520         SendToICS(ics_prefix);
12521         SendToICS("accept\n");
12522     } else if (cmailMsgLoaded) {
12523         if (currentMove == cmailOldMove &&
12524             commentList[cmailOldMove] != NULL &&
12525             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12526                    "Black offers a draw" : "White offers a draw")) {
12527             TruncateGame();
12528             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12529             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12530         } else {
12531             DisplayError(_("There is no pending offer on this move"), 0);
12532             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12533         }
12534     } else {
12535         /* Not used for offers from chess program */
12536     }
12537 }
12538
12539 void
12540 DeclineEvent()
12541 {
12542     /* Decline a pending offer of any kind from opponent */
12543
12544     if (appData.icsActive) {
12545         SendToICS(ics_prefix);
12546         SendToICS("decline\n");
12547     } else if (cmailMsgLoaded) {
12548         if (currentMove == cmailOldMove &&
12549             commentList[cmailOldMove] != NULL &&
12550             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12551                    "Black offers a draw" : "White offers a draw")) {
12552 #ifdef NOTDEF
12553             AppendComment(cmailOldMove, "Draw declined", TRUE);
12554             DisplayComment(cmailOldMove - 1, "Draw declined");
12555 #endif /*NOTDEF*/
12556         } else {
12557             DisplayError(_("There is no pending offer on this move"), 0);
12558         }
12559     } else {
12560         /* Not used for offers from chess program */
12561     }
12562 }
12563
12564 void
12565 RematchEvent()
12566 {
12567     /* Issue ICS rematch command */
12568     if (appData.icsActive) {
12569         SendToICS(ics_prefix);
12570         SendToICS("rematch\n");
12571     }
12572 }
12573
12574 void
12575 CallFlagEvent()
12576 {
12577     /* Call your opponent's flag (claim a win on time) */
12578     if (appData.icsActive) {
12579         SendToICS(ics_prefix);
12580         SendToICS("flag\n");
12581     } else {
12582         switch (gameMode) {
12583           default:
12584             return;
12585           case MachinePlaysWhite:
12586             if (whiteFlag) {
12587                 if (blackFlag)
12588                   GameEnds(GameIsDrawn, "Both players ran out of time",
12589                            GE_PLAYER);
12590                 else
12591                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12592             } else {
12593                 DisplayError(_("Your opponent is not out of time"), 0);
12594             }
12595             break;
12596           case MachinePlaysBlack:
12597             if (blackFlag) {
12598                 if (whiteFlag)
12599                   GameEnds(GameIsDrawn, "Both players ran out of time",
12600                            GE_PLAYER);
12601                 else
12602                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12603             } else {
12604                 DisplayError(_("Your opponent is not out of time"), 0);
12605             }
12606             break;
12607         }
12608     }
12609 }
12610
12611 void
12612 ClockClick(int which)
12613 {       // [HGM] code moved to back-end from winboard.c
12614         if(which) { // black clock
12615           if (gameMode == EditPosition || gameMode == IcsExamining) {
12616             SetBlackToPlayEvent();
12617           } else if (gameMode == EditGame || shiftKey) {
12618             AdjustClock(which, -1);
12619           } else if (gameMode == IcsPlayingWhite ||
12620                      gameMode == MachinePlaysBlack) {
12621             CallFlagEvent();
12622           }
12623         } else { // white clock
12624           if (gameMode == EditPosition || gameMode == IcsExamining) {
12625             SetWhiteToPlayEvent();
12626           } else if (gameMode == EditGame || shiftKey) {
12627             AdjustClock(which, -1);
12628           } else if (gameMode == IcsPlayingBlack ||
12629                    gameMode == MachinePlaysWhite) {
12630             CallFlagEvent();
12631           }
12632         }
12633 }
12634
12635 void
12636 DrawEvent()
12637 {
12638     /* Offer draw or accept pending draw offer from opponent */
12639
12640     if (appData.icsActive) {
12641         /* Note: tournament rules require draw offers to be
12642            made after you make your move but before you punch
12643            your clock.  Currently ICS doesn't let you do that;
12644            instead, you immediately punch your clock after making
12645            a move, but you can offer a draw at any time. */
12646
12647         SendToICS(ics_prefix);
12648         SendToICS("draw\n");
12649         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12650     } else if (cmailMsgLoaded) {
12651         if (currentMove == cmailOldMove &&
12652             commentList[cmailOldMove] != NULL &&
12653             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12654                    "Black offers a draw" : "White offers a draw")) {
12655             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12656             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12657         } else if (currentMove == cmailOldMove + 1) {
12658             char *offer = WhiteOnMove(cmailOldMove) ?
12659               "White offers a draw" : "Black offers a draw";
12660             AppendComment(currentMove, offer, TRUE);
12661             DisplayComment(currentMove - 1, offer);
12662             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12663         } else {
12664             DisplayError(_("You must make your move before offering a draw"), 0);
12665             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12666         }
12667     } else if (first.offeredDraw) {
12668         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12669     } else {
12670         if (first.sendDrawOffers) {
12671             SendToProgram("draw\n", &first);
12672             userOfferedDraw = TRUE;
12673         }
12674     }
12675 }
12676
12677 void
12678 AdjournEvent()
12679 {
12680     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12681
12682     if (appData.icsActive) {
12683         SendToICS(ics_prefix);
12684         SendToICS("adjourn\n");
12685     } else {
12686         /* Currently GNU Chess doesn't offer or accept Adjourns */
12687     }
12688 }
12689
12690
12691 void
12692 AbortEvent()
12693 {
12694     /* Offer Abort or accept pending Abort offer from opponent */
12695
12696     if (appData.icsActive) {
12697         SendToICS(ics_prefix);
12698         SendToICS("abort\n");
12699     } else {
12700         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12701     }
12702 }
12703
12704 void
12705 ResignEvent()
12706 {
12707     /* Resign.  You can do this even if it's not your turn. */
12708
12709     if (appData.icsActive) {
12710         SendToICS(ics_prefix);
12711         SendToICS("resign\n");
12712     } else {
12713         switch (gameMode) {
12714           case MachinePlaysWhite:
12715             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12716             break;
12717           case MachinePlaysBlack:
12718             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12719             break;
12720           case EditGame:
12721             if (cmailMsgLoaded) {
12722                 TruncateGame();
12723                 if (WhiteOnMove(cmailOldMove)) {
12724                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12725                 } else {
12726                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12727                 }
12728                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12729             }
12730             break;
12731           default:
12732             break;
12733         }
12734     }
12735 }
12736
12737
12738 void
12739 StopObservingEvent()
12740 {
12741     /* Stop observing current games */
12742     SendToICS(ics_prefix);
12743     SendToICS("unobserve\n");
12744 }
12745
12746 void
12747 StopExaminingEvent()
12748 {
12749     /* Stop observing current game */
12750     SendToICS(ics_prefix);
12751     SendToICS("unexamine\n");
12752 }
12753
12754 void
12755 ForwardInner(target)
12756      int target;
12757 {
12758     int limit;
12759
12760     if (appData.debugMode)
12761         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12762                 target, currentMove, forwardMostMove);
12763
12764     if (gameMode == EditPosition)
12765       return;
12766
12767     if (gameMode == PlayFromGameFile && !pausing)
12768       PauseEvent();
12769
12770     if (gameMode == IcsExamining && pausing)
12771       limit = pauseExamForwardMostMove;
12772     else
12773       limit = forwardMostMove;
12774
12775     if (target > limit) target = limit;
12776
12777     if (target > 0 && moveList[target - 1][0]) {
12778         int fromX, fromY, toX, toY;
12779         toX = moveList[target - 1][2] - AAA;
12780         toY = moveList[target - 1][3] - ONE;
12781         if (moveList[target - 1][1] == '@') {
12782             if (appData.highlightLastMove) {
12783                 SetHighlights(-1, -1, toX, toY);
12784             }
12785         } else {
12786             fromX = moveList[target - 1][0] - AAA;
12787             fromY = moveList[target - 1][1] - ONE;
12788             if (target == currentMove + 1) {
12789                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12790             }
12791             if (appData.highlightLastMove) {
12792                 SetHighlights(fromX, fromY, toX, toY);
12793             }
12794         }
12795     }
12796     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12797         gameMode == Training || gameMode == PlayFromGameFile ||
12798         gameMode == AnalyzeFile) {
12799         while (currentMove < target) {
12800             SendMoveToProgram(currentMove++, &first);
12801         }
12802     } else {
12803         currentMove = target;
12804     }
12805
12806     if (gameMode == EditGame || gameMode == EndOfGame) {
12807         whiteTimeRemaining = timeRemaining[0][currentMove];
12808         blackTimeRemaining = timeRemaining[1][currentMove];
12809     }
12810     DisplayBothClocks();
12811     DisplayMove(currentMove - 1);
12812     DrawPosition(FALSE, boards[currentMove]);
12813     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12814     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12815         DisplayComment(currentMove - 1, commentList[currentMove]);
12816     }
12817 }
12818
12819
12820 void
12821 ForwardEvent()
12822 {
12823     if (gameMode == IcsExamining && !pausing) {
12824         SendToICS(ics_prefix);
12825         SendToICS("forward\n");
12826     } else {
12827         ForwardInner(currentMove + 1);
12828     }
12829 }
12830
12831 void
12832 ToEndEvent()
12833 {
12834     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12835         /* to optimze, we temporarily turn off analysis mode while we feed
12836          * the remaining moves to the engine. Otherwise we get analysis output
12837          * after each move.
12838          */
12839         if (first.analysisSupport) {
12840           SendToProgram("exit\nforce\n", &first);
12841           first.analyzing = FALSE;
12842         }
12843     }
12844
12845     if (gameMode == IcsExamining && !pausing) {
12846         SendToICS(ics_prefix);
12847         SendToICS("forward 999999\n");
12848     } else {
12849         ForwardInner(forwardMostMove);
12850     }
12851
12852     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12853         /* we have fed all the moves, so reactivate analysis mode */
12854         SendToProgram("analyze\n", &first);
12855         first.analyzing = TRUE;
12856         /*first.maybeThinking = TRUE;*/
12857         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12858     }
12859 }
12860
12861 void
12862 BackwardInner(target)
12863      int target;
12864 {
12865     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12866
12867     if (appData.debugMode)
12868         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12869                 target, currentMove, forwardMostMove);
12870
12871     if (gameMode == EditPosition) return;
12872     if (currentMove <= backwardMostMove) {
12873         ClearHighlights();
12874         DrawPosition(full_redraw, boards[currentMove]);
12875         return;
12876     }
12877     if (gameMode == PlayFromGameFile && !pausing)
12878       PauseEvent();
12879
12880     if (moveList[target][0]) {
12881         int fromX, fromY, toX, toY;
12882         toX = moveList[target][2] - AAA;
12883         toY = moveList[target][3] - ONE;
12884         if (moveList[target][1] == '@') {
12885             if (appData.highlightLastMove) {
12886                 SetHighlights(-1, -1, toX, toY);
12887             }
12888         } else {
12889             fromX = moveList[target][0] - AAA;
12890             fromY = moveList[target][1] - ONE;
12891             if (target == currentMove - 1) {
12892                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12893             }
12894             if (appData.highlightLastMove) {
12895                 SetHighlights(fromX, fromY, toX, toY);
12896             }
12897         }
12898     }
12899     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12900         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12901         while (currentMove > target) {
12902             SendToProgram("undo\n", &first);
12903             currentMove--;
12904         }
12905     } else {
12906         currentMove = target;
12907     }
12908
12909     if (gameMode == EditGame || gameMode == EndOfGame) {
12910         whiteTimeRemaining = timeRemaining[0][currentMove];
12911         blackTimeRemaining = timeRemaining[1][currentMove];
12912     }
12913     DisplayBothClocks();
12914     DisplayMove(currentMove - 1);
12915     DrawPosition(full_redraw, boards[currentMove]);
12916     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12917     // [HGM] PV info: routine tests if comment empty
12918     DisplayComment(currentMove - 1, commentList[currentMove]);
12919 }
12920
12921 void
12922 BackwardEvent()
12923 {
12924     if (gameMode == IcsExamining && !pausing) {
12925         SendToICS(ics_prefix);
12926         SendToICS("backward\n");
12927     } else {
12928         BackwardInner(currentMove - 1);
12929     }
12930 }
12931
12932 void
12933 ToStartEvent()
12934 {
12935     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12936         /* to optimize, we temporarily turn off analysis mode while we undo
12937          * all the moves. Otherwise we get analysis output after each undo.
12938          */
12939         if (first.analysisSupport) {
12940           SendToProgram("exit\nforce\n", &first);
12941           first.analyzing = FALSE;
12942         }
12943     }
12944
12945     if (gameMode == IcsExamining && !pausing) {
12946         SendToICS(ics_prefix);
12947         SendToICS("backward 999999\n");
12948     } else {
12949         BackwardInner(backwardMostMove);
12950     }
12951
12952     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12953         /* we have fed all the moves, so reactivate analysis mode */
12954         SendToProgram("analyze\n", &first);
12955         first.analyzing = TRUE;
12956         /*first.maybeThinking = TRUE;*/
12957         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12958     }
12959 }
12960
12961 void
12962 ToNrEvent(int to)
12963 {
12964   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12965   if (to >= forwardMostMove) to = forwardMostMove;
12966   if (to <= backwardMostMove) to = backwardMostMove;
12967   if (to < currentMove) {
12968     BackwardInner(to);
12969   } else {
12970     ForwardInner(to);
12971   }
12972 }
12973
12974 void
12975 RevertEvent(Boolean annotate)
12976 {
12977     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12978         return;
12979     }
12980     if (gameMode != IcsExamining) {
12981         DisplayError(_("You are not examining a game"), 0);
12982         return;
12983     }
12984     if (pausing) {
12985         DisplayError(_("You can't revert while pausing"), 0);
12986         return;
12987     }
12988     SendToICS(ics_prefix);
12989     SendToICS("revert\n");
12990 }
12991
12992 void
12993 RetractMoveEvent()
12994 {
12995     switch (gameMode) {
12996       case MachinePlaysWhite:
12997       case MachinePlaysBlack:
12998         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12999             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13000             return;
13001         }
13002         if (forwardMostMove < 2) return;
13003         currentMove = forwardMostMove = forwardMostMove - 2;
13004         whiteTimeRemaining = timeRemaining[0][currentMove];
13005         blackTimeRemaining = timeRemaining[1][currentMove];
13006         DisplayBothClocks();
13007         DisplayMove(currentMove - 1);
13008         ClearHighlights();/*!! could figure this out*/
13009         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13010         SendToProgram("remove\n", &first);
13011         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13012         break;
13013
13014       case BeginningOfGame:
13015       default:
13016         break;
13017
13018       case IcsPlayingWhite:
13019       case IcsPlayingBlack:
13020         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13021             SendToICS(ics_prefix);
13022             SendToICS("takeback 2\n");
13023         } else {
13024             SendToICS(ics_prefix);
13025             SendToICS("takeback 1\n");
13026         }
13027         break;
13028     }
13029 }
13030
13031 void
13032 MoveNowEvent()
13033 {
13034     ChessProgramState *cps;
13035
13036     switch (gameMode) {
13037       case MachinePlaysWhite:
13038         if (!WhiteOnMove(forwardMostMove)) {
13039             DisplayError(_("It is your turn"), 0);
13040             return;
13041         }
13042         cps = &first;
13043         break;
13044       case MachinePlaysBlack:
13045         if (WhiteOnMove(forwardMostMove)) {
13046             DisplayError(_("It is your turn"), 0);
13047             return;
13048         }
13049         cps = &first;
13050         break;
13051       case TwoMachinesPlay:
13052         if (WhiteOnMove(forwardMostMove) ==
13053             (first.twoMachinesColor[0] == 'w')) {
13054             cps = &first;
13055         } else {
13056             cps = &second;
13057         }
13058         break;
13059       case BeginningOfGame:
13060       default:
13061         return;
13062     }
13063     SendToProgram("?\n", cps);
13064 }
13065
13066 void
13067 TruncateGameEvent()
13068 {
13069     EditGameEvent();
13070     if (gameMode != EditGame) return;
13071     TruncateGame();
13072 }
13073
13074 void
13075 TruncateGame()
13076 {
13077     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13078     if (forwardMostMove > currentMove) {
13079         if (gameInfo.resultDetails != NULL) {
13080             free(gameInfo.resultDetails);
13081             gameInfo.resultDetails = NULL;
13082             gameInfo.result = GameUnfinished;
13083         }
13084         forwardMostMove = currentMove;
13085         HistorySet(parseList, backwardMostMove, forwardMostMove,
13086                    currentMove-1);
13087     }
13088 }
13089
13090 void
13091 HintEvent()
13092 {
13093     if (appData.noChessProgram) return;
13094     switch (gameMode) {
13095       case MachinePlaysWhite:
13096         if (WhiteOnMove(forwardMostMove)) {
13097             DisplayError(_("Wait until your turn"), 0);
13098             return;
13099         }
13100         break;
13101       case BeginningOfGame:
13102       case MachinePlaysBlack:
13103         if (!WhiteOnMove(forwardMostMove)) {
13104             DisplayError(_("Wait until your turn"), 0);
13105             return;
13106         }
13107         break;
13108       default:
13109         DisplayError(_("No hint available"), 0);
13110         return;
13111     }
13112     SendToProgram("hint\n", &first);
13113     hintRequested = TRUE;
13114 }
13115
13116 void
13117 BookEvent()
13118 {
13119     if (appData.noChessProgram) return;
13120     switch (gameMode) {
13121       case MachinePlaysWhite:
13122         if (WhiteOnMove(forwardMostMove)) {
13123             DisplayError(_("Wait until your turn"), 0);
13124             return;
13125         }
13126         break;
13127       case BeginningOfGame:
13128       case MachinePlaysBlack:
13129         if (!WhiteOnMove(forwardMostMove)) {
13130             DisplayError(_("Wait until your turn"), 0);
13131             return;
13132         }
13133         break;
13134       case EditPosition:
13135         EditPositionDone(TRUE);
13136         break;
13137       case TwoMachinesPlay:
13138         return;
13139       default:
13140         break;
13141     }
13142     SendToProgram("bk\n", &first);
13143     bookOutput[0] = NULLCHAR;
13144     bookRequested = TRUE;
13145 }
13146
13147 void
13148 AboutGameEvent()
13149 {
13150     char *tags = PGNTags(&gameInfo);
13151     TagsPopUp(tags, CmailMsg());
13152     free(tags);
13153 }
13154
13155 /* end button procedures */
13156
13157 void
13158 PrintPosition(fp, move)
13159      FILE *fp;
13160      int move;
13161 {
13162     int i, j;
13163
13164     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13165         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13166             char c = PieceToChar(boards[move][i][j]);
13167             fputc(c == 'x' ? '.' : c, fp);
13168             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13169         }
13170     }
13171     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13172       fprintf(fp, "white to play\n");
13173     else
13174       fprintf(fp, "black to play\n");
13175 }
13176
13177 void
13178 PrintOpponents(fp)
13179      FILE *fp;
13180 {
13181     if (gameInfo.white != NULL) {
13182         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13183     } else {
13184         fprintf(fp, "\n");
13185     }
13186 }
13187
13188 /* Find last component of program's own name, using some heuristics */
13189 void
13190 TidyProgramName(prog, host, buf)
13191      char *prog, *host, buf[MSG_SIZ];
13192 {
13193     char *p, *q;
13194     int local = (strcmp(host, "localhost") == 0);
13195     while (!local && (p = strchr(prog, ';')) != NULL) {
13196         p++;
13197         while (*p == ' ') p++;
13198         prog = p;
13199     }
13200     if (*prog == '"' || *prog == '\'') {
13201         q = strchr(prog + 1, *prog);
13202     } else {
13203         q = strchr(prog, ' ');
13204     }
13205     if (q == NULL) q = prog + strlen(prog);
13206     p = q;
13207     while (p >= prog && *p != '/' && *p != '\\') p--;
13208     p++;
13209     if(p == prog && *p == '"') p++;
13210     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13211     memcpy(buf, p, q - p);
13212     buf[q - p] = NULLCHAR;
13213     if (!local) {
13214         strcat(buf, "@");
13215         strcat(buf, host);
13216     }
13217 }
13218
13219 char *
13220 TimeControlTagValue()
13221 {
13222     char buf[MSG_SIZ];
13223     if (!appData.clockMode) {
13224       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13225     } else if (movesPerSession > 0) {
13226       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13227     } else if (timeIncrement == 0) {
13228       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13229     } else {
13230       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13231     }
13232     return StrSave(buf);
13233 }
13234
13235 void
13236 SetGameInfo()
13237 {
13238     /* This routine is used only for certain modes */
13239     VariantClass v = gameInfo.variant;
13240     ChessMove r = GameUnfinished;
13241     char *p = NULL;
13242
13243     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13244         r = gameInfo.result;
13245         p = gameInfo.resultDetails;
13246         gameInfo.resultDetails = NULL;
13247     }
13248     ClearGameInfo(&gameInfo);
13249     gameInfo.variant = v;
13250
13251     switch (gameMode) {
13252       case MachinePlaysWhite:
13253         gameInfo.event = StrSave( appData.pgnEventHeader );
13254         gameInfo.site = StrSave(HostName());
13255         gameInfo.date = PGNDate();
13256         gameInfo.round = StrSave("-");
13257         gameInfo.white = StrSave(first.tidy);
13258         gameInfo.black = StrSave(UserName());
13259         gameInfo.timeControl = TimeControlTagValue();
13260         break;
13261
13262       case MachinePlaysBlack:
13263         gameInfo.event = StrSave( appData.pgnEventHeader );
13264         gameInfo.site = StrSave(HostName());
13265         gameInfo.date = PGNDate();
13266         gameInfo.round = StrSave("-");
13267         gameInfo.white = StrSave(UserName());
13268         gameInfo.black = StrSave(first.tidy);
13269         gameInfo.timeControl = TimeControlTagValue();
13270         break;
13271
13272       case TwoMachinesPlay:
13273         gameInfo.event = StrSave( appData.pgnEventHeader );
13274         gameInfo.site = StrSave(HostName());
13275         gameInfo.date = PGNDate();
13276         if (matchGame > 0) {
13277             char buf[MSG_SIZ];
13278             snprintf(buf, MSG_SIZ, "%d", matchGame);
13279             gameInfo.round = StrSave(buf);
13280         } else {
13281             gameInfo.round = StrSave("-");
13282         }
13283         if (first.twoMachinesColor[0] == 'w') {
13284             gameInfo.white = StrSave(first.tidy);
13285             gameInfo.black = StrSave(second.tidy);
13286         } else {
13287             gameInfo.white = StrSave(second.tidy);
13288             gameInfo.black = StrSave(first.tidy);
13289         }
13290         gameInfo.timeControl = TimeControlTagValue();
13291         break;
13292
13293       case EditGame:
13294         gameInfo.event = StrSave("Edited game");
13295         gameInfo.site = StrSave(HostName());
13296         gameInfo.date = PGNDate();
13297         gameInfo.round = StrSave("-");
13298         gameInfo.white = StrSave("-");
13299         gameInfo.black = StrSave("-");
13300         gameInfo.result = r;
13301         gameInfo.resultDetails = p;
13302         break;
13303
13304       case EditPosition:
13305         gameInfo.event = StrSave("Edited position");
13306         gameInfo.site = StrSave(HostName());
13307         gameInfo.date = PGNDate();
13308         gameInfo.round = StrSave("-");
13309         gameInfo.white = StrSave("-");
13310         gameInfo.black = StrSave("-");
13311         break;
13312
13313       case IcsPlayingWhite:
13314       case IcsPlayingBlack:
13315       case IcsObserving:
13316       case IcsExamining:
13317         break;
13318
13319       case PlayFromGameFile:
13320         gameInfo.event = StrSave("Game from non-PGN file");
13321         gameInfo.site = StrSave(HostName());
13322         gameInfo.date = PGNDate();
13323         gameInfo.round = StrSave("-");
13324         gameInfo.white = StrSave("?");
13325         gameInfo.black = StrSave("?");
13326         break;
13327
13328       default:
13329         break;
13330     }
13331 }
13332
13333 void
13334 ReplaceComment(index, text)
13335      int index;
13336      char *text;
13337 {
13338     int len;
13339     char *p;
13340     float score;
13341
13342     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13343        pvInfoList[index-1].depth == len &&
13344        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13345        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13346     while (*text == '\n') text++;
13347     len = strlen(text);
13348     while (len > 0 && text[len - 1] == '\n') len--;
13349
13350     if (commentList[index] != NULL)
13351       free(commentList[index]);
13352
13353     if (len == 0) {
13354         commentList[index] = NULL;
13355         return;
13356     }
13357   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13358       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13359       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13360     commentList[index] = (char *) malloc(len + 2);
13361     strncpy(commentList[index], text, len);
13362     commentList[index][len] = '\n';
13363     commentList[index][len + 1] = NULLCHAR;
13364   } else {
13365     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13366     char *p;
13367     commentList[index] = (char *) malloc(len + 7);
13368     safeStrCpy(commentList[index], "{\n", 3);
13369     safeStrCpy(commentList[index]+2, text, len+1);
13370     commentList[index][len+2] = NULLCHAR;
13371     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13372     strcat(commentList[index], "\n}\n");
13373   }
13374 }
13375
13376 void
13377 CrushCRs(text)
13378      char *text;
13379 {
13380   char *p = text;
13381   char *q = text;
13382   char ch;
13383
13384   do {
13385     ch = *p++;
13386     if (ch == '\r') continue;
13387     *q++ = ch;
13388   } while (ch != '\0');
13389 }
13390
13391 void
13392 AppendComment(index, text, addBraces)
13393      int index;
13394      char *text;
13395      Boolean addBraces; // [HGM] braces: tells if we should add {}
13396 {
13397     int oldlen, len;
13398     char *old;
13399
13400 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13401     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13402
13403     CrushCRs(text);
13404     while (*text == '\n') text++;
13405     len = strlen(text);
13406     while (len > 0 && text[len - 1] == '\n') len--;
13407
13408     if (len == 0) return;
13409
13410     if (commentList[index] != NULL) {
13411         old = commentList[index];
13412         oldlen = strlen(old);
13413         while(commentList[index][oldlen-1] ==  '\n')
13414           commentList[index][--oldlen] = NULLCHAR;
13415         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13416         safeStrCpy(commentList[index], old, oldlen + len + 6);
13417         free(old);
13418         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13419         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13420           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13421           while (*text == '\n') { text++; len--; }
13422           commentList[index][--oldlen] = NULLCHAR;
13423       }
13424         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13425         else          strcat(commentList[index], "\n");
13426         strcat(commentList[index], text);
13427         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13428         else          strcat(commentList[index], "\n");
13429     } else {
13430         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13431         if(addBraces)
13432           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13433         else commentList[index][0] = NULLCHAR;
13434         strcat(commentList[index], text);
13435         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13436         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13437     }
13438 }
13439
13440 static char * FindStr( char * text, char * sub_text )
13441 {
13442     char * result = strstr( text, sub_text );
13443
13444     if( result != NULL ) {
13445         result += strlen( sub_text );
13446     }
13447
13448     return result;
13449 }
13450
13451 /* [AS] Try to extract PV info from PGN comment */
13452 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13453 char *GetInfoFromComment( int index, char * text )
13454 {
13455     char * sep = text, *p;
13456
13457     if( text != NULL && index > 0 ) {
13458         int score = 0;
13459         int depth = 0;
13460         int time = -1, sec = 0, deci;
13461         char * s_eval = FindStr( text, "[%eval " );
13462         char * s_emt = FindStr( text, "[%emt " );
13463
13464         if( s_eval != NULL || s_emt != NULL ) {
13465             /* New style */
13466             char delim;
13467
13468             if( s_eval != NULL ) {
13469                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13470                     return text;
13471                 }
13472
13473                 if( delim != ']' ) {
13474                     return text;
13475                 }
13476             }
13477
13478             if( s_emt != NULL ) {
13479             }
13480                 return text;
13481         }
13482         else {
13483             /* We expect something like: [+|-]nnn.nn/dd */
13484             int score_lo = 0;
13485
13486             if(*text != '{') return text; // [HGM] braces: must be normal comment
13487
13488             sep = strchr( text, '/' );
13489             if( sep == NULL || sep < (text+4) ) {
13490                 return text;
13491             }
13492
13493             p = text;
13494             if(p[1] == '(') { // comment starts with PV
13495                p = strchr(p, ')'); // locate end of PV
13496                if(p == NULL || sep < p+5) return text;
13497                // at this point we have something like "{(.*) +0.23/6 ..."
13498                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13499                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13500                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13501             }
13502             time = -1; sec = -1; deci = -1;
13503             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13504                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13505                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13506                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13507                 return text;
13508             }
13509
13510             if( score_lo < 0 || score_lo >= 100 ) {
13511                 return text;
13512             }
13513
13514             if(sec >= 0) time = 600*time + 10*sec; else
13515             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13516
13517             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13518
13519             /* [HGM] PV time: now locate end of PV info */
13520             while( *++sep >= '0' && *sep <= '9'); // strip depth
13521             if(time >= 0)
13522             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13523             if(sec >= 0)
13524             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13525             if(deci >= 0)
13526             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13527             while(*sep == ' ') sep++;
13528         }
13529
13530         if( depth <= 0 ) {
13531             return text;
13532         }
13533
13534         if( time < 0 ) {
13535             time = -1;
13536         }
13537
13538         pvInfoList[index-1].depth = depth;
13539         pvInfoList[index-1].score = score;
13540         pvInfoList[index-1].time  = 10*time; // centi-sec
13541         if(*sep == '}') *sep = 0; else *--sep = '{';
13542         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13543     }
13544     return sep;
13545 }
13546
13547 void
13548 SendToProgram(message, cps)
13549      char *message;
13550      ChessProgramState *cps;
13551 {
13552     int count, outCount, error;
13553     char buf[MSG_SIZ];
13554
13555     if (cps->pr == NULL) return;
13556     Attention(cps);
13557
13558     if (appData.debugMode) {
13559         TimeMark now;
13560         GetTimeMark(&now);
13561         fprintf(debugFP, "%ld >%-6s: %s",
13562                 SubtractTimeMarks(&now, &programStartTime),
13563                 cps->which, message);
13564     }
13565
13566     count = strlen(message);
13567     outCount = OutputToProcess(cps->pr, message, count, &error);
13568     if (outCount < count && !exiting
13569                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13570       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13571         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13572             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13573                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13574                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13575             } else {
13576                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13577             }
13578             gameInfo.resultDetails = StrSave(buf);
13579         }
13580         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13581     }
13582 }
13583
13584 void
13585 ReceiveFromProgram(isr, closure, message, count, error)
13586      InputSourceRef isr;
13587      VOIDSTAR closure;
13588      char *message;
13589      int count;
13590      int error;
13591 {
13592     char *end_str;
13593     char buf[MSG_SIZ];
13594     ChessProgramState *cps = (ChessProgramState *)closure;
13595
13596     if (isr != cps->isr) return; /* Killed intentionally */
13597     if (count <= 0) {
13598         if (count == 0) {
13599             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13600                     _(cps->which), cps->program);
13601         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13602                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13603                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13604                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13605                 } else {
13606                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13607                 }
13608                 gameInfo.resultDetails = StrSave(buf);
13609             }
13610             RemoveInputSource(cps->isr);
13611             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13612         } else {
13613             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13614                     _(cps->which), cps->program);
13615             RemoveInputSource(cps->isr);
13616
13617             /* [AS] Program is misbehaving badly... kill it */
13618             if( count == -2 ) {
13619                 DestroyChildProcess( cps->pr, 9 );
13620                 cps->pr = NoProc;
13621             }
13622
13623             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13624         }
13625         return;
13626     }
13627
13628     if ((end_str = strchr(message, '\r')) != NULL)
13629       *end_str = NULLCHAR;
13630     if ((end_str = strchr(message, '\n')) != NULL)
13631       *end_str = NULLCHAR;
13632
13633     if (appData.debugMode) {
13634         TimeMark now; int print = 1;
13635         char *quote = ""; char c; int i;
13636
13637         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13638                 char start = message[0];
13639                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13640                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13641                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13642                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13643                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13644                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13645                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13646                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13647                    sscanf(message, "hint: %c", &c)!=1 && 
13648                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13649                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13650                     print = (appData.engineComments >= 2);
13651                 }
13652                 message[0] = start; // restore original message
13653         }
13654         if(print) {
13655                 GetTimeMark(&now);
13656                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13657                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13658                         quote,
13659                         message);
13660         }
13661     }
13662
13663     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13664     if (appData.icsEngineAnalyze) {
13665         if (strstr(message, "whisper") != NULL ||
13666              strstr(message, "kibitz") != NULL ||
13667             strstr(message, "tellics") != NULL) return;
13668     }
13669
13670     HandleMachineMove(message, cps);
13671 }
13672
13673
13674 void
13675 SendTimeControl(cps, mps, tc, inc, sd, st)
13676      ChessProgramState *cps;
13677      int mps, inc, sd, st;
13678      long tc;
13679 {
13680     char buf[MSG_SIZ];
13681     int seconds;
13682
13683     if( timeControl_2 > 0 ) {
13684         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13685             tc = timeControl_2;
13686         }
13687     }
13688     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13689     inc /= cps->timeOdds;
13690     st  /= cps->timeOdds;
13691
13692     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13693
13694     if (st > 0) {
13695       /* Set exact time per move, normally using st command */
13696       if (cps->stKludge) {
13697         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13698         seconds = st % 60;
13699         if (seconds == 0) {
13700           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13701         } else {
13702           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13703         }
13704       } else {
13705         snprintf(buf, MSG_SIZ, "st %d\n", st);
13706       }
13707     } else {
13708       /* Set conventional or incremental time control, using level command */
13709       if (seconds == 0) {
13710         /* Note old gnuchess bug -- minutes:seconds used to not work.
13711            Fixed in later versions, but still avoid :seconds
13712            when seconds is 0. */
13713         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13714       } else {
13715         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13716                  seconds, inc/1000.);
13717       }
13718     }
13719     SendToProgram(buf, cps);
13720
13721     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13722     /* Orthogonally, limit search to given depth */
13723     if (sd > 0) {
13724       if (cps->sdKludge) {
13725         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13726       } else {
13727         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13728       }
13729       SendToProgram(buf, cps);
13730     }
13731
13732     if(cps->nps >= 0) { /* [HGM] nps */
13733         if(cps->supportsNPS == FALSE)
13734           cps->nps = -1; // don't use if engine explicitly says not supported!
13735         else {
13736           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13737           SendToProgram(buf, cps);
13738         }
13739     }
13740 }
13741
13742 ChessProgramState *WhitePlayer()
13743 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13744 {
13745     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13746        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13747         return &second;
13748     return &first;
13749 }
13750
13751 void
13752 SendTimeRemaining(cps, machineWhite)
13753      ChessProgramState *cps;
13754      int /*boolean*/ machineWhite;
13755 {
13756     char message[MSG_SIZ];
13757     long time, otime;
13758
13759     /* Note: this routine must be called when the clocks are stopped
13760        or when they have *just* been set or switched; otherwise
13761        it will be off by the time since the current tick started.
13762     */
13763     if (machineWhite) {
13764         time = whiteTimeRemaining / 10;
13765         otime = blackTimeRemaining / 10;
13766     } else {
13767         time = blackTimeRemaining / 10;
13768         otime = whiteTimeRemaining / 10;
13769     }
13770     /* [HGM] translate opponent's time by time-odds factor */
13771     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13772     if (appData.debugMode) {
13773         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13774     }
13775
13776     if (time <= 0) time = 1;
13777     if (otime <= 0) otime = 1;
13778
13779     snprintf(message, MSG_SIZ, "time %ld\n", time);
13780     SendToProgram(message, cps);
13781
13782     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13783     SendToProgram(message, cps);
13784 }
13785
13786 int
13787 BoolFeature(p, name, loc, cps)
13788      char **p;
13789      char *name;
13790      int *loc;
13791      ChessProgramState *cps;
13792 {
13793   char buf[MSG_SIZ];
13794   int len = strlen(name);
13795   int val;
13796
13797   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13798     (*p) += len + 1;
13799     sscanf(*p, "%d", &val);
13800     *loc = (val != 0);
13801     while (**p && **p != ' ')
13802       (*p)++;
13803     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13804     SendToProgram(buf, cps);
13805     return TRUE;
13806   }
13807   return FALSE;
13808 }
13809
13810 int
13811 IntFeature(p, name, loc, cps)
13812      char **p;
13813      char *name;
13814      int *loc;
13815      ChessProgramState *cps;
13816 {
13817   char buf[MSG_SIZ];
13818   int len = strlen(name);
13819   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13820     (*p) += len + 1;
13821     sscanf(*p, "%d", loc);
13822     while (**p && **p != ' ') (*p)++;
13823     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13824     SendToProgram(buf, cps);
13825     return TRUE;
13826   }
13827   return FALSE;
13828 }
13829
13830 int
13831 StringFeature(p, name, loc, cps)
13832      char **p;
13833      char *name;
13834      char loc[];
13835      ChessProgramState *cps;
13836 {
13837   char buf[MSG_SIZ];
13838   int len = strlen(name);
13839   if (strncmp((*p), name, len) == 0
13840       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13841     (*p) += len + 2;
13842     sscanf(*p, "%[^\"]", loc);
13843     while (**p && **p != '\"') (*p)++;
13844     if (**p == '\"') (*p)++;
13845     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13846     SendToProgram(buf, cps);
13847     return TRUE;
13848   }
13849   return FALSE;
13850 }
13851
13852 int
13853 ParseOption(Option *opt, ChessProgramState *cps)
13854 // [HGM] options: process the string that defines an engine option, and determine
13855 // name, type, default value, and allowed value range
13856 {
13857         char *p, *q, buf[MSG_SIZ];
13858         int n, min = (-1)<<31, max = 1<<31, def;
13859
13860         if(p = strstr(opt->name, " -spin ")) {
13861             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13862             if(max < min) max = min; // enforce consistency
13863             if(def < min) def = min;
13864             if(def > max) def = max;
13865             opt->value = def;
13866             opt->min = min;
13867             opt->max = max;
13868             opt->type = Spin;
13869         } else if((p = strstr(opt->name, " -slider "))) {
13870             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13871             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13872             if(max < min) max = min; // enforce consistency
13873             if(def < min) def = min;
13874             if(def > max) def = max;
13875             opt->value = def;
13876             opt->min = min;
13877             opt->max = max;
13878             opt->type = Spin; // Slider;
13879         } else if((p = strstr(opt->name, " -string "))) {
13880             opt->textValue = p+9;
13881             opt->type = TextBox;
13882         } else if((p = strstr(opt->name, " -file "))) {
13883             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13884             opt->textValue = p+7;
13885             opt->type = TextBox; // FileName;
13886         } else if((p = strstr(opt->name, " -path "))) {
13887             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13888             opt->textValue = p+7;
13889             opt->type = TextBox; // PathName;
13890         } else if(p = strstr(opt->name, " -check ")) {
13891             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13892             opt->value = (def != 0);
13893             opt->type = CheckBox;
13894         } else if(p = strstr(opt->name, " -combo ")) {
13895             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13896             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13897             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13898             opt->value = n = 0;
13899             while(q = StrStr(q, " /// ")) {
13900                 n++; *q = 0;    // count choices, and null-terminate each of them
13901                 q += 5;
13902                 if(*q == '*') { // remember default, which is marked with * prefix
13903                     q++;
13904                     opt->value = n;
13905                 }
13906                 cps->comboList[cps->comboCnt++] = q;
13907             }
13908             cps->comboList[cps->comboCnt++] = NULL;
13909             opt->max = n + 1;
13910             opt->type = ComboBox;
13911         } else if(p = strstr(opt->name, " -button")) {
13912             opt->type = Button;
13913         } else if(p = strstr(opt->name, " -save")) {
13914             opt->type = SaveButton;
13915         } else return FALSE;
13916         *p = 0; // terminate option name
13917         // now look if the command-line options define a setting for this engine option.
13918         if(cps->optionSettings && cps->optionSettings[0])
13919             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13920         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13921           snprintf(buf, MSG_SIZ, "option %s", p);
13922                 if(p = strstr(buf, ",")) *p = 0;
13923                 if(q = strchr(buf, '=')) switch(opt->type) {
13924                     case ComboBox:
13925                         for(n=0; n<opt->max; n++)
13926                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13927                         break;
13928                     case TextBox:
13929                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13930                         break;
13931                     case Spin:
13932                     case CheckBox:
13933                         opt->value = atoi(q+1);
13934                     default:
13935                         break;
13936                 }
13937                 strcat(buf, "\n");
13938                 SendToProgram(buf, cps);
13939         }
13940         return TRUE;
13941 }
13942
13943 void
13944 FeatureDone(cps, val)
13945      ChessProgramState* cps;
13946      int val;
13947 {
13948   DelayedEventCallback cb = GetDelayedEvent();
13949   if ((cb == InitBackEnd3 && cps == &first) ||
13950       (cb == SettingsMenuIfReady && cps == &second) ||
13951       (cb == TwoMachinesEventIfReady && cps == &second)) {
13952     CancelDelayedEvent();
13953     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13954   }
13955   cps->initDone = val;
13956 }
13957
13958 /* Parse feature command from engine */
13959 void
13960 ParseFeatures(args, cps)
13961      char* args;
13962      ChessProgramState *cps;
13963 {
13964   char *p = args;
13965   char *q;
13966   int val;
13967   char buf[MSG_SIZ];
13968
13969   for (;;) {
13970     while (*p == ' ') p++;
13971     if (*p == NULLCHAR) return;
13972
13973     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13974     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13975     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13976     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13977     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13978     if (BoolFeature(&p, "reuse", &val, cps)) {
13979       /* Engine can disable reuse, but can't enable it if user said no */
13980       if (!val) cps->reuse = FALSE;
13981       continue;
13982     }
13983     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13984     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13985       if (gameMode == TwoMachinesPlay) {
13986         DisplayTwoMachinesTitle();
13987       } else {
13988         DisplayTitle("");
13989       }
13990       continue;
13991     }
13992     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13993     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13994     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13995     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13996     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13997     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13998     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13999     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14000     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14001     if (IntFeature(&p, "done", &val, cps)) {
14002       FeatureDone(cps, val);
14003       continue;
14004     }
14005     /* Added by Tord: */
14006     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14007     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14008     /* End of additions by Tord */
14009
14010     /* [HGM] added features: */
14011     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14012     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14013     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14014     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14015     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14016     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14017     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14018         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14019           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14020             SendToProgram(buf, cps);
14021             continue;
14022         }
14023         if(cps->nrOptions >= MAX_OPTIONS) {
14024             cps->nrOptions--;
14025             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14026             DisplayError(buf, 0);
14027         }
14028         continue;
14029     }
14030     /* End of additions by HGM */
14031
14032     /* unknown feature: complain and skip */
14033     q = p;
14034     while (*q && *q != '=') q++;
14035     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14036     SendToProgram(buf, cps);
14037     p = q;
14038     if (*p == '=') {
14039       p++;
14040       if (*p == '\"') {
14041         p++;
14042         while (*p && *p != '\"') p++;
14043         if (*p == '\"') p++;
14044       } else {
14045         while (*p && *p != ' ') p++;
14046       }
14047     }
14048   }
14049
14050 }
14051
14052 void
14053 PeriodicUpdatesEvent(newState)
14054      int newState;
14055 {
14056     if (newState == appData.periodicUpdates)
14057       return;
14058
14059     appData.periodicUpdates=newState;
14060
14061     /* Display type changes, so update it now */
14062 //    DisplayAnalysis();
14063
14064     /* Get the ball rolling again... */
14065     if (newState) {
14066         AnalysisPeriodicEvent(1);
14067         StartAnalysisClock();
14068     }
14069 }
14070
14071 void
14072 PonderNextMoveEvent(newState)
14073      int newState;
14074 {
14075     if (newState == appData.ponderNextMove) return;
14076     if (gameMode == EditPosition) EditPositionDone(TRUE);
14077     if (newState) {
14078         SendToProgram("hard\n", &first);
14079         if (gameMode == TwoMachinesPlay) {
14080             SendToProgram("hard\n", &second);
14081         }
14082     } else {
14083         SendToProgram("easy\n", &first);
14084         thinkOutput[0] = NULLCHAR;
14085         if (gameMode == TwoMachinesPlay) {
14086             SendToProgram("easy\n", &second);
14087         }
14088     }
14089     appData.ponderNextMove = newState;
14090 }
14091
14092 void
14093 NewSettingEvent(option, feature, command, value)
14094      char *command;
14095      int option, value, *feature;
14096 {
14097     char buf[MSG_SIZ];
14098
14099     if (gameMode == EditPosition) EditPositionDone(TRUE);
14100     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14101     if(feature == NULL || *feature) SendToProgram(buf, &first);
14102     if (gameMode == TwoMachinesPlay) {
14103         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14104     }
14105 }
14106
14107 void
14108 ShowThinkingEvent()
14109 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14110 {
14111     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14112     int newState = appData.showThinking
14113         // [HGM] thinking: other features now need thinking output as well
14114         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14115
14116     if (oldState == newState) return;
14117     oldState = newState;
14118     if (gameMode == EditPosition) EditPositionDone(TRUE);
14119     if (oldState) {
14120         SendToProgram("post\n", &first);
14121         if (gameMode == TwoMachinesPlay) {
14122             SendToProgram("post\n", &second);
14123         }
14124     } else {
14125         SendToProgram("nopost\n", &first);
14126         thinkOutput[0] = NULLCHAR;
14127         if (gameMode == TwoMachinesPlay) {
14128             SendToProgram("nopost\n", &second);
14129         }
14130     }
14131 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14132 }
14133
14134 void
14135 AskQuestionEvent(title, question, replyPrefix, which)
14136      char *title; char *question; char *replyPrefix; char *which;
14137 {
14138   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14139   if (pr == NoProc) return;
14140   AskQuestion(title, question, replyPrefix, pr);
14141 }
14142
14143 void
14144 DisplayMove(moveNumber)
14145      int moveNumber;
14146 {
14147     char message[MSG_SIZ];
14148     char res[MSG_SIZ];
14149     char cpThinkOutput[MSG_SIZ];
14150
14151     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14152
14153     if (moveNumber == forwardMostMove - 1 ||
14154         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14155
14156         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14157
14158         if (strchr(cpThinkOutput, '\n')) {
14159             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14160         }
14161     } else {
14162         *cpThinkOutput = NULLCHAR;
14163     }
14164
14165     /* [AS] Hide thinking from human user */
14166     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14167         *cpThinkOutput = NULLCHAR;
14168         if( thinkOutput[0] != NULLCHAR ) {
14169             int i;
14170
14171             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14172                 cpThinkOutput[i] = '.';
14173             }
14174             cpThinkOutput[i] = NULLCHAR;
14175             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14176         }
14177     }
14178
14179     if (moveNumber == forwardMostMove - 1 &&
14180         gameInfo.resultDetails != NULL) {
14181         if (gameInfo.resultDetails[0] == NULLCHAR) {
14182           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14183         } else {
14184           snprintf(res, MSG_SIZ, " {%s} %s",
14185                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14186         }
14187     } else {
14188         res[0] = NULLCHAR;
14189     }
14190
14191     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14192         DisplayMessage(res, cpThinkOutput);
14193     } else {
14194       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14195                 WhiteOnMove(moveNumber) ? " " : ".. ",
14196                 parseList[moveNumber], res);
14197         DisplayMessage(message, cpThinkOutput);
14198     }
14199 }
14200
14201 void
14202 DisplayComment(moveNumber, text)
14203      int moveNumber;
14204      char *text;
14205 {
14206     char title[MSG_SIZ];
14207     char buf[8000]; // comment can be long!
14208     int score, depth;
14209
14210     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14211       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14212     } else {
14213       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14214               WhiteOnMove(moveNumber) ? " " : ".. ",
14215               parseList[moveNumber]);
14216     }
14217     // [HGM] PV info: display PV info together with (or as) comment
14218     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14219       if(text == NULL) text = "";
14220       score = pvInfoList[moveNumber].score;
14221       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14222               depth, (pvInfoList[moveNumber].time+50)/100, text);
14223       text = buf;
14224     }
14225     if (text != NULL && (appData.autoDisplayComment || commentUp))
14226         CommentPopUp(title, text);
14227 }
14228
14229 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14230  * might be busy thinking or pondering.  It can be omitted if your
14231  * gnuchess is configured to stop thinking immediately on any user
14232  * input.  However, that gnuchess feature depends on the FIONREAD
14233  * ioctl, which does not work properly on some flavors of Unix.
14234  */
14235 void
14236 Attention(cps)
14237      ChessProgramState *cps;
14238 {
14239 #if ATTENTION
14240     if (!cps->useSigint) return;
14241     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14242     switch (gameMode) {
14243       case MachinePlaysWhite:
14244       case MachinePlaysBlack:
14245       case TwoMachinesPlay:
14246       case IcsPlayingWhite:
14247       case IcsPlayingBlack:
14248       case AnalyzeMode:
14249       case AnalyzeFile:
14250         /* Skip if we know it isn't thinking */
14251         if (!cps->maybeThinking) return;
14252         if (appData.debugMode)
14253           fprintf(debugFP, "Interrupting %s\n", cps->which);
14254         InterruptChildProcess(cps->pr);
14255         cps->maybeThinking = FALSE;
14256         break;
14257       default:
14258         break;
14259     }
14260 #endif /*ATTENTION*/
14261 }
14262
14263 int
14264 CheckFlags()
14265 {
14266     if (whiteTimeRemaining <= 0) {
14267         if (!whiteFlag) {
14268             whiteFlag = TRUE;
14269             if (appData.icsActive) {
14270                 if (appData.autoCallFlag &&
14271                     gameMode == IcsPlayingBlack && !blackFlag) {
14272                   SendToICS(ics_prefix);
14273                   SendToICS("flag\n");
14274                 }
14275             } else {
14276                 if (blackFlag) {
14277                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14278                 } else {
14279                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14280                     if (appData.autoCallFlag) {
14281                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14282                         return TRUE;
14283                     }
14284                 }
14285             }
14286         }
14287     }
14288     if (blackTimeRemaining <= 0) {
14289         if (!blackFlag) {
14290             blackFlag = TRUE;
14291             if (appData.icsActive) {
14292                 if (appData.autoCallFlag &&
14293                     gameMode == IcsPlayingWhite && !whiteFlag) {
14294                   SendToICS(ics_prefix);
14295                   SendToICS("flag\n");
14296                 }
14297             } else {
14298                 if (whiteFlag) {
14299                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14300                 } else {
14301                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14302                     if (appData.autoCallFlag) {
14303                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14304                         return TRUE;
14305                     }
14306                 }
14307             }
14308         }
14309     }
14310     return FALSE;
14311 }
14312
14313 void
14314 CheckTimeControl()
14315 {
14316     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14317         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14318
14319     /*
14320      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14321      */
14322     if ( !WhiteOnMove(forwardMostMove) ) {
14323         /* White made time control */
14324         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14325         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14326         /* [HGM] time odds: correct new time quota for time odds! */
14327                                             / WhitePlayer()->timeOdds;
14328         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14329     } else {
14330         lastBlack -= blackTimeRemaining;
14331         /* Black made time control */
14332         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14333                                             / WhitePlayer()->other->timeOdds;
14334         lastWhite = whiteTimeRemaining;
14335     }
14336 }
14337
14338 void
14339 DisplayBothClocks()
14340 {
14341     int wom = gameMode == EditPosition ?
14342       !blackPlaysFirst : WhiteOnMove(currentMove);
14343     DisplayWhiteClock(whiteTimeRemaining, wom);
14344     DisplayBlackClock(blackTimeRemaining, !wom);
14345 }
14346
14347
14348 /* Timekeeping seems to be a portability nightmare.  I think everyone
14349    has ftime(), but I'm really not sure, so I'm including some ifdefs
14350    to use other calls if you don't.  Clocks will be less accurate if
14351    you have neither ftime nor gettimeofday.
14352 */
14353
14354 /* VS 2008 requires the #include outside of the function */
14355 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14356 #include <sys/timeb.h>
14357 #endif
14358
14359 /* Get the current time as a TimeMark */
14360 void
14361 GetTimeMark(tm)
14362      TimeMark *tm;
14363 {
14364 #if HAVE_GETTIMEOFDAY
14365
14366     struct timeval timeVal;
14367     struct timezone timeZone;
14368
14369     gettimeofday(&timeVal, &timeZone);
14370     tm->sec = (long) timeVal.tv_sec;
14371     tm->ms = (int) (timeVal.tv_usec / 1000L);
14372
14373 #else /*!HAVE_GETTIMEOFDAY*/
14374 #if HAVE_FTIME
14375
14376 // include <sys/timeb.h> / moved to just above start of function
14377     struct timeb timeB;
14378
14379     ftime(&timeB);
14380     tm->sec = (long) timeB.time;
14381     tm->ms = (int) timeB.millitm;
14382
14383 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14384     tm->sec = (long) time(NULL);
14385     tm->ms = 0;
14386 #endif
14387 #endif
14388 }
14389
14390 /* Return the difference in milliseconds between two
14391    time marks.  We assume the difference will fit in a long!
14392 */
14393 long
14394 SubtractTimeMarks(tm2, tm1)
14395      TimeMark *tm2, *tm1;
14396 {
14397     return 1000L*(tm2->sec - tm1->sec) +
14398            (long) (tm2->ms - tm1->ms);
14399 }
14400
14401
14402 /*
14403  * Code to manage the game clocks.
14404  *
14405  * In tournament play, black starts the clock and then white makes a move.
14406  * We give the human user a slight advantage if he is playing white---the
14407  * clocks don't run until he makes his first move, so it takes zero time.
14408  * Also, we don't account for network lag, so we could get out of sync
14409  * with GNU Chess's clock -- but then, referees are always right.
14410  */
14411
14412 static TimeMark tickStartTM;
14413 static long intendedTickLength;
14414
14415 long
14416 NextTickLength(timeRemaining)
14417      long timeRemaining;
14418 {
14419     long nominalTickLength, nextTickLength;
14420
14421     if (timeRemaining > 0L && timeRemaining <= 10000L)
14422       nominalTickLength = 100L;
14423     else
14424       nominalTickLength = 1000L;
14425     nextTickLength = timeRemaining % nominalTickLength;
14426     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14427
14428     return nextTickLength;
14429 }
14430
14431 /* Adjust clock one minute up or down */
14432 void
14433 AdjustClock(Boolean which, int dir)
14434 {
14435     if(which) blackTimeRemaining += 60000*dir;
14436     else      whiteTimeRemaining += 60000*dir;
14437     DisplayBothClocks();
14438 }
14439
14440 /* Stop clocks and reset to a fresh time control */
14441 void
14442 ResetClocks()
14443 {
14444     (void) StopClockTimer();
14445     if (appData.icsActive) {
14446         whiteTimeRemaining = blackTimeRemaining = 0;
14447     } else if (searchTime) {
14448         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14449         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14450     } else { /* [HGM] correct new time quote for time odds */
14451         whiteTC = blackTC = fullTimeControlString;
14452         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14453         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14454     }
14455     if (whiteFlag || blackFlag) {
14456         DisplayTitle("");
14457         whiteFlag = blackFlag = FALSE;
14458     }
14459     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14460     DisplayBothClocks();
14461 }
14462
14463 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14464
14465 /* Decrement running clock by amount of time that has passed */
14466 void
14467 DecrementClocks()
14468 {
14469     long timeRemaining;
14470     long lastTickLength, fudge;
14471     TimeMark now;
14472
14473     if (!appData.clockMode) return;
14474     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14475
14476     GetTimeMark(&now);
14477
14478     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14479
14480     /* Fudge if we woke up a little too soon */
14481     fudge = intendedTickLength - lastTickLength;
14482     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14483
14484     if (WhiteOnMove(forwardMostMove)) {
14485         if(whiteNPS >= 0) lastTickLength = 0;
14486         timeRemaining = whiteTimeRemaining -= lastTickLength;
14487         if(timeRemaining < 0 && !appData.icsActive) {
14488             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14489             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14490                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14491                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14492             }
14493         }
14494         DisplayWhiteClock(whiteTimeRemaining - fudge,
14495                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14496     } else {
14497         if(blackNPS >= 0) lastTickLength = 0;
14498         timeRemaining = blackTimeRemaining -= lastTickLength;
14499         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14500             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14501             if(suddenDeath) {
14502                 blackStartMove = forwardMostMove;
14503                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14504             }
14505         }
14506         DisplayBlackClock(blackTimeRemaining - fudge,
14507                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14508     }
14509     if (CheckFlags()) return;
14510
14511     tickStartTM = now;
14512     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14513     StartClockTimer(intendedTickLength);
14514
14515     /* if the time remaining has fallen below the alarm threshold, sound the
14516      * alarm. if the alarm has sounded and (due to a takeback or time control
14517      * with increment) the time remaining has increased to a level above the
14518      * threshold, reset the alarm so it can sound again.
14519      */
14520
14521     if (appData.icsActive && appData.icsAlarm) {
14522
14523         /* make sure we are dealing with the user's clock */
14524         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14525                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14526            )) return;
14527
14528         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14529             alarmSounded = FALSE;
14530         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14531             PlayAlarmSound();
14532             alarmSounded = TRUE;
14533         }
14534     }
14535 }
14536
14537
14538 /* A player has just moved, so stop the previously running
14539    clock and (if in clock mode) start the other one.
14540    We redisplay both clocks in case we're in ICS mode, because
14541    ICS gives us an update to both clocks after every move.
14542    Note that this routine is called *after* forwardMostMove
14543    is updated, so the last fractional tick must be subtracted
14544    from the color that is *not* on move now.
14545 */
14546 void
14547 SwitchClocks(int newMoveNr)
14548 {
14549     long lastTickLength;
14550     TimeMark now;
14551     int flagged = FALSE;
14552
14553     GetTimeMark(&now);
14554
14555     if (StopClockTimer() && appData.clockMode) {
14556         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14557         if (!WhiteOnMove(forwardMostMove)) {
14558             if(blackNPS >= 0) lastTickLength = 0;
14559             blackTimeRemaining -= lastTickLength;
14560            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14561 //         if(pvInfoList[forwardMostMove].time == -1)
14562                  pvInfoList[forwardMostMove].time =               // use GUI time
14563                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14564         } else {
14565            if(whiteNPS >= 0) lastTickLength = 0;
14566            whiteTimeRemaining -= lastTickLength;
14567            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14568 //         if(pvInfoList[forwardMostMove].time == -1)
14569                  pvInfoList[forwardMostMove].time =
14570                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14571         }
14572         flagged = CheckFlags();
14573     }
14574     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14575     CheckTimeControl();
14576
14577     if (flagged || !appData.clockMode) return;
14578
14579     switch (gameMode) {
14580       case MachinePlaysBlack:
14581       case MachinePlaysWhite:
14582       case BeginningOfGame:
14583         if (pausing) return;
14584         break;
14585
14586       case EditGame:
14587       case PlayFromGameFile:
14588       case IcsExamining:
14589         return;
14590
14591       default:
14592         break;
14593     }
14594
14595     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14596         if(WhiteOnMove(forwardMostMove))
14597              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14598         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14599     }
14600
14601     tickStartTM = now;
14602     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14603       whiteTimeRemaining : blackTimeRemaining);
14604     StartClockTimer(intendedTickLength);
14605 }
14606
14607
14608 /* Stop both clocks */
14609 void
14610 StopClocks()
14611 {
14612     long lastTickLength;
14613     TimeMark now;
14614
14615     if (!StopClockTimer()) return;
14616     if (!appData.clockMode) return;
14617
14618     GetTimeMark(&now);
14619
14620     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14621     if (WhiteOnMove(forwardMostMove)) {
14622         if(whiteNPS >= 0) lastTickLength = 0;
14623         whiteTimeRemaining -= lastTickLength;
14624         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14625     } else {
14626         if(blackNPS >= 0) lastTickLength = 0;
14627         blackTimeRemaining -= lastTickLength;
14628         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14629     }
14630     CheckFlags();
14631 }
14632
14633 /* Start clock of player on move.  Time may have been reset, so
14634    if clock is already running, stop and restart it. */
14635 void
14636 StartClocks()
14637 {
14638     (void) StopClockTimer(); /* in case it was running already */
14639     DisplayBothClocks();
14640     if (CheckFlags()) return;
14641
14642     if (!appData.clockMode) return;
14643     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14644
14645     GetTimeMark(&tickStartTM);
14646     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14647       whiteTimeRemaining : blackTimeRemaining);
14648
14649    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14650     whiteNPS = blackNPS = -1;
14651     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14652        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14653         whiteNPS = first.nps;
14654     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14655        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14656         blackNPS = first.nps;
14657     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14658         whiteNPS = second.nps;
14659     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14660         blackNPS = second.nps;
14661     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14662
14663     StartClockTimer(intendedTickLength);
14664 }
14665
14666 char *
14667 TimeString(ms)
14668      long ms;
14669 {
14670     long second, minute, hour, day;
14671     char *sign = "";
14672     static char buf[32];
14673
14674     if (ms > 0 && ms <= 9900) {
14675       /* convert milliseconds to tenths, rounding up */
14676       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14677
14678       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14679       return buf;
14680     }
14681
14682     /* convert milliseconds to seconds, rounding up */
14683     /* use floating point to avoid strangeness of integer division
14684        with negative dividends on many machines */
14685     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14686
14687     if (second < 0) {
14688         sign = "-";
14689         second = -second;
14690     }
14691
14692     day = second / (60 * 60 * 24);
14693     second = second % (60 * 60 * 24);
14694     hour = second / (60 * 60);
14695     second = second % (60 * 60);
14696     minute = second / 60;
14697     second = second % 60;
14698
14699     if (day > 0)
14700       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14701               sign, day, hour, minute, second);
14702     else if (hour > 0)
14703       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14704     else
14705       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14706
14707     return buf;
14708 }
14709
14710
14711 /*
14712  * This is necessary because some C libraries aren't ANSI C compliant yet.
14713  */
14714 char *
14715 StrStr(string, match)
14716      char *string, *match;
14717 {
14718     int i, length;
14719
14720     length = strlen(match);
14721
14722     for (i = strlen(string) - length; i >= 0; i--, string++)
14723       if (!strncmp(match, string, length))
14724         return string;
14725
14726     return NULL;
14727 }
14728
14729 char *
14730 StrCaseStr(string, match)
14731      char *string, *match;
14732 {
14733     int i, j, length;
14734
14735     length = strlen(match);
14736
14737     for (i = strlen(string) - length; i >= 0; i--, string++) {
14738         for (j = 0; j < length; j++) {
14739             if (ToLower(match[j]) != ToLower(string[j]))
14740               break;
14741         }
14742         if (j == length) return string;
14743     }
14744
14745     return NULL;
14746 }
14747
14748 #ifndef _amigados
14749 int
14750 StrCaseCmp(s1, s2)
14751      char *s1, *s2;
14752 {
14753     char c1, c2;
14754
14755     for (;;) {
14756         c1 = ToLower(*s1++);
14757         c2 = ToLower(*s2++);
14758         if (c1 > c2) return 1;
14759         if (c1 < c2) return -1;
14760         if (c1 == NULLCHAR) return 0;
14761     }
14762 }
14763
14764
14765 int
14766 ToLower(c)
14767      int c;
14768 {
14769     return isupper(c) ? tolower(c) : c;
14770 }
14771
14772
14773 int
14774 ToUpper(c)
14775      int c;
14776 {
14777     return islower(c) ? toupper(c) : c;
14778 }
14779 #endif /* !_amigados    */
14780
14781 char *
14782 StrSave(s)
14783      char *s;
14784 {
14785   char *ret;
14786
14787   if ((ret = (char *) malloc(strlen(s) + 1)))
14788     {
14789       safeStrCpy(ret, s, strlen(s)+1);
14790     }
14791   return ret;
14792 }
14793
14794 char *
14795 StrSavePtr(s, savePtr)
14796      char *s, **savePtr;
14797 {
14798     if (*savePtr) {
14799         free(*savePtr);
14800     }
14801     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14802       safeStrCpy(*savePtr, s, strlen(s)+1);
14803     }
14804     return(*savePtr);
14805 }
14806
14807 char *
14808 PGNDate()
14809 {
14810     time_t clock;
14811     struct tm *tm;
14812     char buf[MSG_SIZ];
14813
14814     clock = time((time_t *)NULL);
14815     tm = localtime(&clock);
14816     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14817             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14818     return StrSave(buf);
14819 }
14820
14821
14822 char *
14823 PositionToFEN(move, overrideCastling)
14824      int move;
14825      char *overrideCastling;
14826 {
14827     int i, j, fromX, fromY, toX, toY;
14828     int whiteToPlay;
14829     char buf[128];
14830     char *p, *q;
14831     int emptycount;
14832     ChessSquare piece;
14833
14834     whiteToPlay = (gameMode == EditPosition) ?
14835       !blackPlaysFirst : (move % 2 == 0);
14836     p = buf;
14837
14838     /* Piece placement data */
14839     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14840         emptycount = 0;
14841         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14842             if (boards[move][i][j] == EmptySquare) {
14843                 emptycount++;
14844             } else { ChessSquare piece = boards[move][i][j];
14845                 if (emptycount > 0) {
14846                     if(emptycount<10) /* [HGM] can be >= 10 */
14847                         *p++ = '0' + emptycount;
14848                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14849                     emptycount = 0;
14850                 }
14851                 if(PieceToChar(piece) == '+') {
14852                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14853                     *p++ = '+';
14854                     piece = (ChessSquare)(DEMOTED piece);
14855                 }
14856                 *p++ = PieceToChar(piece);
14857                 if(p[-1] == '~') {
14858                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14859                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14860                     *p++ = '~';
14861                 }
14862             }
14863         }
14864         if (emptycount > 0) {
14865             if(emptycount<10) /* [HGM] can be >= 10 */
14866                 *p++ = '0' + emptycount;
14867             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14868             emptycount = 0;
14869         }
14870         *p++ = '/';
14871     }
14872     *(p - 1) = ' ';
14873
14874     /* [HGM] print Crazyhouse or Shogi holdings */
14875     if( gameInfo.holdingsWidth ) {
14876         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14877         q = p;
14878         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14879             piece = boards[move][i][BOARD_WIDTH-1];
14880             if( piece != EmptySquare )
14881               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14882                   *p++ = PieceToChar(piece);
14883         }
14884         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14885             piece = boards[move][BOARD_HEIGHT-i-1][0];
14886             if( piece != EmptySquare )
14887               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14888                   *p++ = PieceToChar(piece);
14889         }
14890
14891         if( q == p ) *p++ = '-';
14892         *p++ = ']';
14893         *p++ = ' ';
14894     }
14895
14896     /* Active color */
14897     *p++ = whiteToPlay ? 'w' : 'b';
14898     *p++ = ' ';
14899
14900   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14901     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14902   } else {
14903   if(nrCastlingRights) {
14904      q = p;
14905      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14906        /* [HGM] write directly from rights */
14907            if(boards[move][CASTLING][2] != NoRights &&
14908               boards[move][CASTLING][0] != NoRights   )
14909                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14910            if(boards[move][CASTLING][2] != NoRights &&
14911               boards[move][CASTLING][1] != NoRights   )
14912                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14913            if(boards[move][CASTLING][5] != NoRights &&
14914               boards[move][CASTLING][3] != NoRights   )
14915                 *p++ = boards[move][CASTLING][3] + AAA;
14916            if(boards[move][CASTLING][5] != NoRights &&
14917               boards[move][CASTLING][4] != NoRights   )
14918                 *p++ = boards[move][CASTLING][4] + AAA;
14919      } else {
14920
14921         /* [HGM] write true castling rights */
14922         if( nrCastlingRights == 6 ) {
14923             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14924                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14925             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14926                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14927             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14928                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14929             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14930                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14931         }
14932      }
14933      if (q == p) *p++ = '-'; /* No castling rights */
14934      *p++ = ' ';
14935   }
14936
14937   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14938      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14939     /* En passant target square */
14940     if (move > backwardMostMove) {
14941         fromX = moveList[move - 1][0] - AAA;
14942         fromY = moveList[move - 1][1] - ONE;
14943         toX = moveList[move - 1][2] - AAA;
14944         toY = moveList[move - 1][3] - ONE;
14945         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14946             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14947             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14948             fromX == toX) {
14949             /* 2-square pawn move just happened */
14950             *p++ = toX + AAA;
14951             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14952         } else {
14953             *p++ = '-';
14954         }
14955     } else if(move == backwardMostMove) {
14956         // [HGM] perhaps we should always do it like this, and forget the above?
14957         if((signed char)boards[move][EP_STATUS] >= 0) {
14958             *p++ = boards[move][EP_STATUS] + AAA;
14959             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14960         } else {
14961             *p++ = '-';
14962         }
14963     } else {
14964         *p++ = '-';
14965     }
14966     *p++ = ' ';
14967   }
14968   }
14969
14970     /* [HGM] find reversible plies */
14971     {   int i = 0, j=move;
14972
14973         if (appData.debugMode) { int k;
14974             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14975             for(k=backwardMostMove; k<=forwardMostMove; k++)
14976                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14977
14978         }
14979
14980         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14981         if( j == backwardMostMove ) i += initialRulePlies;
14982         sprintf(p, "%d ", i);
14983         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14984     }
14985     /* Fullmove number */
14986     sprintf(p, "%d", (move / 2) + 1);
14987
14988     return StrSave(buf);
14989 }
14990
14991 Boolean
14992 ParseFEN(board, blackPlaysFirst, fen)
14993     Board board;
14994      int *blackPlaysFirst;
14995      char *fen;
14996 {
14997     int i, j;
14998     char *p, c;
14999     int emptycount;
15000     ChessSquare piece;
15001
15002     p = fen;
15003
15004     /* [HGM] by default clear Crazyhouse holdings, if present */
15005     if(gameInfo.holdingsWidth) {
15006        for(i=0; i<BOARD_HEIGHT; i++) {
15007            board[i][0]             = EmptySquare; /* black holdings */
15008            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15009            board[i][1]             = (ChessSquare) 0; /* black counts */
15010            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15011        }
15012     }
15013
15014     /* Piece placement data */
15015     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15016         j = 0;
15017         for (;;) {
15018             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15019                 if (*p == '/') p++;
15020                 emptycount = gameInfo.boardWidth - j;
15021                 while (emptycount--)
15022                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15023                 break;
15024 #if(BOARD_FILES >= 10)
15025             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15026                 p++; emptycount=10;
15027                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15028                 while (emptycount--)
15029                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15030 #endif
15031             } else if (isdigit(*p)) {
15032                 emptycount = *p++ - '0';
15033                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15034                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15035                 while (emptycount--)
15036                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15037             } else if (*p == '+' || isalpha(*p)) {
15038                 if (j >= gameInfo.boardWidth) return FALSE;
15039                 if(*p=='+') {
15040                     piece = CharToPiece(*++p);
15041                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15042                     piece = (ChessSquare) (PROMOTED piece ); p++;
15043                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15044                 } else piece = CharToPiece(*p++);
15045
15046                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15047                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15048                     piece = (ChessSquare) (PROMOTED piece);
15049                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15050                     p++;
15051                 }
15052                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15053             } else {
15054                 return FALSE;
15055             }
15056         }
15057     }
15058     while (*p == '/' || *p == ' ') p++;
15059
15060     /* [HGM] look for Crazyhouse holdings here */
15061     while(*p==' ') p++;
15062     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15063         if(*p == '[') p++;
15064         if(*p == '-' ) p++; /* empty holdings */ else {
15065             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15066             /* if we would allow FEN reading to set board size, we would   */
15067             /* have to add holdings and shift the board read so far here   */
15068             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15069                 p++;
15070                 if((int) piece >= (int) BlackPawn ) {
15071                     i = (int)piece - (int)BlackPawn;
15072                     i = PieceToNumber((ChessSquare)i);
15073                     if( i >= gameInfo.holdingsSize ) return FALSE;
15074                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15075                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15076                 } else {
15077                     i = (int)piece - (int)WhitePawn;
15078                     i = PieceToNumber((ChessSquare)i);
15079                     if( i >= gameInfo.holdingsSize ) return FALSE;
15080                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15081                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15082                 }
15083             }
15084         }
15085         if(*p == ']') p++;
15086     }
15087
15088     while(*p == ' ') p++;
15089
15090     /* Active color */
15091     c = *p++;
15092     if(appData.colorNickNames) {
15093       if( c == appData.colorNickNames[0] ) c = 'w'; else
15094       if( c == appData.colorNickNames[1] ) c = 'b';
15095     }
15096     switch (c) {
15097       case 'w':
15098         *blackPlaysFirst = FALSE;
15099         break;
15100       case 'b':
15101         *blackPlaysFirst = TRUE;
15102         break;
15103       default:
15104         return FALSE;
15105     }
15106
15107     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15108     /* return the extra info in global variiables             */
15109
15110     /* set defaults in case FEN is incomplete */
15111     board[EP_STATUS] = EP_UNKNOWN;
15112     for(i=0; i<nrCastlingRights; i++ ) {
15113         board[CASTLING][i] =
15114             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15115     }   /* assume possible unless obviously impossible */
15116     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15117     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15118     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15119                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15120     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15121     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15122     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15123                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15124     FENrulePlies = 0;
15125
15126     while(*p==' ') p++;
15127     if(nrCastlingRights) {
15128       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15129           /* castling indicator present, so default becomes no castlings */
15130           for(i=0; i<nrCastlingRights; i++ ) {
15131                  board[CASTLING][i] = NoRights;
15132           }
15133       }
15134       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15135              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15136              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15137              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15138         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15139
15140         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15141             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15142             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15143         }
15144         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15145             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15146         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15147                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15148         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15149                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15150         switch(c) {
15151           case'K':
15152               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15153               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15154               board[CASTLING][2] = whiteKingFile;
15155               break;
15156           case'Q':
15157               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15158               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15159               board[CASTLING][2] = whiteKingFile;
15160               break;
15161           case'k':
15162               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15163               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15164               board[CASTLING][5] = blackKingFile;
15165               break;
15166           case'q':
15167               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15168               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15169               board[CASTLING][5] = blackKingFile;
15170           case '-':
15171               break;
15172           default: /* FRC castlings */
15173               if(c >= 'a') { /* black rights */
15174                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15175                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15176                   if(i == BOARD_RGHT) break;
15177                   board[CASTLING][5] = i;
15178                   c -= AAA;
15179                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15180                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15181                   if(c > i)
15182                       board[CASTLING][3] = c;
15183                   else
15184                       board[CASTLING][4] = c;
15185               } else { /* white rights */
15186                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15187                     if(board[0][i] == WhiteKing) break;
15188                   if(i == BOARD_RGHT) break;
15189                   board[CASTLING][2] = i;
15190                   c -= AAA - 'a' + 'A';
15191                   if(board[0][c] >= WhiteKing) break;
15192                   if(c > i)
15193                       board[CASTLING][0] = c;
15194                   else
15195                       board[CASTLING][1] = c;
15196               }
15197         }
15198       }
15199       for(i=0; i<nrCastlingRights; i++)
15200         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15201     if (appData.debugMode) {
15202         fprintf(debugFP, "FEN castling rights:");
15203         for(i=0; i<nrCastlingRights; i++)
15204         fprintf(debugFP, " %d", board[CASTLING][i]);
15205         fprintf(debugFP, "\n");
15206     }
15207
15208       while(*p==' ') p++;
15209     }
15210
15211     /* read e.p. field in games that know e.p. capture */
15212     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15213        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15214       if(*p=='-') {
15215         p++; board[EP_STATUS] = EP_NONE;
15216       } else {
15217          char c = *p++ - AAA;
15218
15219          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15220          if(*p >= '0' && *p <='9') p++;
15221          board[EP_STATUS] = c;
15222       }
15223     }
15224
15225
15226     if(sscanf(p, "%d", &i) == 1) {
15227         FENrulePlies = i; /* 50-move ply counter */
15228         /* (The move number is still ignored)    */
15229     }
15230
15231     return TRUE;
15232 }
15233
15234 void
15235 EditPositionPasteFEN(char *fen)
15236 {
15237   if (fen != NULL) {
15238     Board initial_position;
15239
15240     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15241       DisplayError(_("Bad FEN position in clipboard"), 0);
15242       return ;
15243     } else {
15244       int savedBlackPlaysFirst = blackPlaysFirst;
15245       EditPositionEvent();
15246       blackPlaysFirst = savedBlackPlaysFirst;
15247       CopyBoard(boards[0], initial_position);
15248       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15249       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15250       DisplayBothClocks();
15251       DrawPosition(FALSE, boards[currentMove]);
15252     }
15253   }
15254 }
15255
15256 static char cseq[12] = "\\   ";
15257
15258 Boolean set_cont_sequence(char *new_seq)
15259 {
15260     int len;
15261     Boolean ret;
15262
15263     // handle bad attempts to set the sequence
15264         if (!new_seq)
15265                 return 0; // acceptable error - no debug
15266
15267     len = strlen(new_seq);
15268     ret = (len > 0) && (len < sizeof(cseq));
15269     if (ret)
15270       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15271     else if (appData.debugMode)
15272       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15273     return ret;
15274 }
15275
15276 /*
15277     reformat a source message so words don't cross the width boundary.  internal
15278     newlines are not removed.  returns the wrapped size (no null character unless
15279     included in source message).  If dest is NULL, only calculate the size required
15280     for the dest buffer.  lp argument indicats line position upon entry, and it's
15281     passed back upon exit.
15282 */
15283 int wrap(char *dest, char *src, int count, int width, int *lp)
15284 {
15285     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15286
15287     cseq_len = strlen(cseq);
15288     old_line = line = *lp;
15289     ansi = len = clen = 0;
15290
15291     for (i=0; i < count; i++)
15292     {
15293         if (src[i] == '\033')
15294             ansi = 1;
15295
15296         // if we hit the width, back up
15297         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15298         {
15299             // store i & len in case the word is too long
15300             old_i = i, old_len = len;
15301
15302             // find the end of the last word
15303             while (i && src[i] != ' ' && src[i] != '\n')
15304             {
15305                 i--;
15306                 len--;
15307             }
15308
15309             // word too long?  restore i & len before splitting it
15310             if ((old_i-i+clen) >= width)
15311             {
15312                 i = old_i;
15313                 len = old_len;
15314             }
15315
15316             // extra space?
15317             if (i && src[i-1] == ' ')
15318                 len--;
15319
15320             if (src[i] != ' ' && src[i] != '\n')
15321             {
15322                 i--;
15323                 if (len)
15324                     len--;
15325             }
15326
15327             // now append the newline and continuation sequence
15328             if (dest)
15329                 dest[len] = '\n';
15330             len++;
15331             if (dest)
15332                 strncpy(dest+len, cseq, cseq_len);
15333             len += cseq_len;
15334             line = cseq_len;
15335             clen = cseq_len;
15336             continue;
15337         }
15338
15339         if (dest)
15340             dest[len] = src[i];
15341         len++;
15342         if (!ansi)
15343             line++;
15344         if (src[i] == '\n')
15345             line = 0;
15346         if (src[i] == 'm')
15347             ansi = 0;
15348     }
15349     if (dest && appData.debugMode)
15350     {
15351         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15352             count, width, line, len, *lp);
15353         show_bytes(debugFP, src, count);
15354         fprintf(debugFP, "\ndest: ");
15355         show_bytes(debugFP, dest, len);
15356         fprintf(debugFP, "\n");
15357     }
15358     *lp = dest ? line : old_line;
15359
15360     return len;
15361 }
15362
15363 // [HGM] vari: routines for shelving variations
15364
15365 void
15366 PushTail(int firstMove, int lastMove)
15367 {
15368         int i, j, nrMoves = lastMove - firstMove;
15369
15370         if(appData.icsActive) { // only in local mode
15371                 forwardMostMove = currentMove; // mimic old ICS behavior
15372                 return;
15373         }
15374         if(storedGames >= MAX_VARIATIONS-1) return;
15375
15376         // push current tail of game on stack
15377         savedResult[storedGames] = gameInfo.result;
15378         savedDetails[storedGames] = gameInfo.resultDetails;
15379         gameInfo.resultDetails = NULL;
15380         savedFirst[storedGames] = firstMove;
15381         savedLast [storedGames] = lastMove;
15382         savedFramePtr[storedGames] = framePtr;
15383         framePtr -= nrMoves; // reserve space for the boards
15384         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15385             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15386             for(j=0; j<MOVE_LEN; j++)
15387                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15388             for(j=0; j<2*MOVE_LEN; j++)
15389                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15390             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15391             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15392             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15393             pvInfoList[firstMove+i-1].depth = 0;
15394             commentList[framePtr+i] = commentList[firstMove+i];
15395             commentList[firstMove+i] = NULL;
15396         }
15397
15398         storedGames++;
15399         forwardMostMove = firstMove; // truncate game so we can start variation
15400         if(storedGames == 1) GreyRevert(FALSE);
15401 }
15402
15403 Boolean
15404 PopTail(Boolean annotate)
15405 {
15406         int i, j, nrMoves;
15407         char buf[8000], moveBuf[20];
15408
15409         if(appData.icsActive) return FALSE; // only in local mode
15410         if(!storedGames) return FALSE; // sanity
15411         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15412
15413         storedGames--;
15414         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15415         nrMoves = savedLast[storedGames] - currentMove;
15416         if(annotate) {
15417                 int cnt = 10;
15418                 if(!WhiteOnMove(currentMove))
15419                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15420                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15421                 for(i=currentMove; i<forwardMostMove; i++) {
15422                         if(WhiteOnMove(i))
15423                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15424                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15425                         strcat(buf, moveBuf);
15426                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15427                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15428                 }
15429                 strcat(buf, ")");
15430         }
15431         for(i=1; i<=nrMoves; i++) { // copy last variation back
15432             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15433             for(j=0; j<MOVE_LEN; j++)
15434                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15435             for(j=0; j<2*MOVE_LEN; j++)
15436                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15437             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15438             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15439             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15440             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15441             commentList[currentMove+i] = commentList[framePtr+i];
15442             commentList[framePtr+i] = NULL;
15443         }
15444         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15445         framePtr = savedFramePtr[storedGames];
15446         gameInfo.result = savedResult[storedGames];
15447         if(gameInfo.resultDetails != NULL) {
15448             free(gameInfo.resultDetails);
15449       }
15450         gameInfo.resultDetails = savedDetails[storedGames];
15451         forwardMostMove = currentMove + nrMoves;
15452         if(storedGames == 0) GreyRevert(TRUE);
15453         return TRUE;
15454 }
15455
15456 void
15457 CleanupTail()
15458 {       // remove all shelved variations
15459         int i;
15460         for(i=0; i<storedGames; i++) {
15461             if(savedDetails[i])
15462                 free(savedDetails[i]);
15463             savedDetails[i] = NULL;
15464         }
15465         for(i=framePtr; i<MAX_MOVES; i++) {
15466                 if(commentList[i]) free(commentList[i]);
15467                 commentList[i] = NULL;
15468         }
15469         framePtr = MAX_MOVES-1;
15470         storedGames = 0;
15471 }
15472
15473 void
15474 LoadVariation(int index, char *text)
15475 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15476         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15477         int level = 0, move;
15478
15479         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15480         // first find outermost bracketing variation
15481         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15482             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15483                 if(*p == '{') wait = '}'; else
15484                 if(*p == '[') wait = ']'; else
15485                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15486                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15487             }
15488             if(*p == wait) wait = NULLCHAR; // closing ]} found
15489             p++;
15490         }
15491         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15492         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15493         end[1] = NULLCHAR; // clip off comment beyond variation
15494         ToNrEvent(currentMove-1);
15495         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15496         // kludge: use ParsePV() to append variation to game
15497         move = currentMove;
15498         ParsePV(start, TRUE);
15499         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15500         ClearPremoveHighlights();
15501         CommentPopDown();
15502         ToNrEvent(currentMove+1);
15503 }
15504