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