Add option -absoluteAnalysisScores
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236
237 #ifdef WIN32
238        extern void ConsoleCreate();
239 #endif
240
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
244
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
252 Boolean abortMatch;
253
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 int endPV = -1;
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
261 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
265 Boolean partnerUp;
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
277 int chattingPartner;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
283
284 /* States for ics_getting_history */
285 #define H_FALSE 0
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
291
292 /* whosays values for GameEnds */
293 #define GE_ICS 0
294 #define GE_ENGINE 1
295 #define GE_PLAYER 2
296 #define GE_FILE 3
297 #define GE_XBOARD 4
298 #define GE_ENGINE1 5
299 #define GE_ENGINE2 6
300
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
303
304 /* Different types of move when calling RegisterMove */
305 #define CMAIL_MOVE   0
306 #define CMAIL_RESIGN 1
307 #define CMAIL_DRAW   2
308 #define CMAIL_ACCEPT 3
309
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
314
315 /* Telnet protocol constants */
316 #define TN_WILL 0373
317 #define TN_WONT 0374
318 #define TN_DO   0375
319 #define TN_DONT 0376
320 #define TN_IAC  0377
321 #define TN_ECHO 0001
322 #define TN_SGA  0003
323 #define TN_PORT 23
324
325 char*
326 safeStrCpy( char *dst, const char *src, size_t count )
327 { // [HGM] made safe
328   int i;
329   assert( dst != NULL );
330   assert( src != NULL );
331   assert( count > 0 );
332
333   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334   if(  i == count && dst[count-1] != NULLCHAR)
335     {
336       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337       if(appData.debugMode)
338       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339     }
340
341   return dst;
342 }
343
344 /* Some compiler can't cast u64 to double
345  * This function do the job for us:
346
347  * We use the highest bit for cast, this only
348  * works if the highest bit is not
349  * in use (This should not happen)
350  *
351  * We used this for all compiler
352  */
353 double
354 u64ToDouble(u64 value)
355 {
356   double r;
357   u64 tmp = value & u64Const(0x7fffffffffffffff);
358   r = (double)(s64)tmp;
359   if (value & u64Const(0x8000000000000000))
360        r +=  9.2233720368547758080e18; /* 2^63 */
361  return r;
362 }
363
364 /* Fake up flags for now, as we aren't keeping track of castling
365    availability yet. [HGM] Change of logic: the flag now only
366    indicates the type of castlings allowed by the rule of the game.
367    The actual rights themselves are maintained in the array
368    castlingRights, as part of the game history, and are not probed
369    by this function.
370  */
371 int
372 PosFlags(index)
373 {
374   int flags = F_ALL_CASTLE_OK;
375   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376   switch (gameInfo.variant) {
377   case VariantSuicide:
378     flags &= ~F_ALL_CASTLE_OK;
379   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380     flags |= F_IGNORE_CHECK;
381   case VariantLosers:
382     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
383     break;
384   case VariantAtomic:
385     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386     break;
387   case VariantKriegspiel:
388     flags |= F_KRIEGSPIEL_CAPTURE;
389     break;
390   case VariantCapaRandom:
391   case VariantFischeRandom:
392     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393   case VariantNoCastle:
394   case VariantShatranj:
395   case VariantCourier:
396   case VariantMakruk:
397     flags &= ~F_ALL_CASTLE_OK;
398     break;
399   default:
400     break;
401   }
402   return flags;
403 }
404
405 FILE *gameFileFP, *debugFP;
406
407 /*
408     [AS] Note: sometimes, the sscanf() function is used to parse the input
409     into a fixed-size buffer. Because of this, we must be prepared to
410     receive strings as long as the size of the input buffer, which is currently
411     set to 4K for Windows and 8K for the rest.
412     So, we must either allocate sufficiently large buffers here, or
413     reduce the size of the input buffer in the input reading part.
414 */
415
416 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
417 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
418 char thinkOutput1[MSG_SIZ*10];
419
420 ChessProgramState first, second;
421
422 /* premove variables */
423 int premoveToX = 0;
424 int premoveToY = 0;
425 int premoveFromX = 0;
426 int premoveFromY = 0;
427 int premovePromoChar = 0;
428 int gotPremove = 0;
429 Boolean alarmSounded;
430 /* end premove variables */
431
432 char *ics_prefix = "$";
433 int ics_type = ICS_GENERIC;
434
435 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
436 int pauseExamForwardMostMove = 0;
437 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
438 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
439 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
440 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
441 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
442 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
443 int whiteFlag = FALSE, blackFlag = FALSE;
444 int userOfferedDraw = FALSE;
445 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
446 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
447 int cmailMoveType[CMAIL_MAX_GAMES];
448 long ics_clock_paused = 0;
449 ProcRef icsPR = NoProc, cmailPR = NoProc;
450 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
451 GameMode gameMode = BeginningOfGame;
452 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
453 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
454 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
455 int hiddenThinkOutputState = 0; /* [AS] */
456 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
457 int adjudicateLossPlies = 6;
458 char white_holding[64], black_holding[64];
459 TimeMark lastNodeCountTime;
460 long lastNodeCount=0;
461 int shiftKey; // [HGM] set by mouse handler
462
463 int have_sent_ICS_logon = 0;
464 int movesPerSession;
465 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
466 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
475
476 /* animateTraining preserves the state of appData.animate
477  * when Training mode is activated. This allows the
478  * response to be animated when appData.animate == TRUE and
479  * appData.animateDragging == TRUE.
480  */
481 Boolean animateTraining;
482
483 GameInfo gameInfo;
484
485 AppData appData;
486
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char  initialRights[BOARD_FILES];
491 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int   initialRulePlies, FENrulePlies;
493 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 int loadFlag = 0;
495 int shuffleOpenings;
496 int mute; // mute all sounds
497
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int storedGames = 0;
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
507
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
513
514 ChessSquare  FIDEArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackBishop, BlackKnight, BlackRook }
519 };
520
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525         BlackKing, BlackKing, BlackKnight, BlackRook }
526 };
527
528 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531     { BlackRook, BlackMan, BlackBishop, BlackQueen,
532         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 };
534
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 };
541
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 };
548
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackMan, BlackFerz,
560         BlackKing, BlackMan, BlackKnight, BlackRook }
561 };
562
563
564 #if (BOARD_FILES>=10)
565 ChessSquare ShogiArray[2][BOARD_FILES] = {
566     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
567         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
568     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
569         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
570 };
571
572 ChessSquare XiangqiArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
574         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
576         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
577 };
578
579 ChessSquare CapablancaArray[2][BOARD_FILES] = {
580     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
581         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
583         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
584 };
585
586 ChessSquare GreatArray[2][BOARD_FILES] = {
587     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
588         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
589     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
590         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
591 };
592
593 ChessSquare JanusArray[2][BOARD_FILES] = {
594     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
595         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
596     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
597         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
614         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
616         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating(str)
649   char *str;
650 {
651   while(*str && !isdigit(*str)) ++str;
652   if (!*str)
653     return 0;   /* One of the special "no rating" cases */
654   else
655     return atoi(str);
656 }
657
658 void
659 ClearProgramStats()
660 {
661     /* Init programStats */
662     programStats.movelist[0] = 0;
663     programStats.depth = 0;
664     programStats.nr_moves = 0;
665     programStats.moves_left = 0;
666     programStats.nodes = 0;
667     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
668     programStats.score = 0;
669     programStats.got_only_move = 0;
670     programStats.got_fail = 0;
671     programStats.line_is_book = 0;
672 }
673
674 void
675 CommonEngineInit()
676 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677     if (appData.firstPlaysBlack) {
678         first.twoMachinesColor = "black\n";
679         second.twoMachinesColor = "white\n";
680     } else {
681         first.twoMachinesColor = "white\n";
682         second.twoMachinesColor = "black\n";
683     }
684
685     first.other = &second;
686     second.other = &first;
687
688     { float norm = 1;
689         if(appData.timeOddsMode) {
690             norm = appData.timeOdds[0];
691             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692         }
693         first.timeOdds  = appData.timeOdds[0]/norm;
694         second.timeOdds = appData.timeOdds[1]/norm;
695     }
696
697     if(programVersion) free(programVersion);
698     if (appData.noChessProgram) {
699         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700         sprintf(programVersion, "%s", PACKAGE_STRING);
701     } else {
702       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
705     }
706 }
707
708 void
709 UnloadEngine(ChessProgramState *cps)
710 {
711         /* Kill off first chess program */
712         if (cps->isr != NULL)
713           RemoveInputSource(cps->isr);
714         cps->isr = NULL;
715
716         if (cps->pr != NoProc) {
717             ExitAnalyzeMode();
718             DoSleep( appData.delayBeforeQuit );
719             SendToProgram("quit\n", cps);
720             DoSleep( appData.delayAfterQuit );
721             DestroyChildProcess(cps->pr, cps->useSigterm);
722         }
723         cps->pr = NoProc;
724         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 }
726
727 void
728 ClearOptions(ChessProgramState *cps)
729 {
730     int i;
731     cps->nrOptions = cps->comboCnt = 0;
732     for(i=0; i<MAX_OPTIONS; i++) {
733         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734         cps->option[i].textValue = 0;
735     }
736 }
737
738 char *engineNames[] = {
739 "first",
740 "second"
741 };
742
743 void
744 InitEngine(ChessProgramState *cps, int n)
745 {   // [HGM] all engine initialiation put in a function that does one engine
746
747     ClearOptions(cps);
748
749     cps->which = engineNames[n];
750     cps->maybeThinking = FALSE;
751     cps->pr = NoProc;
752     cps->isr = NULL;
753     cps->sendTime = 2;
754     cps->sendDrawOffers = 1;
755
756     cps->program = appData.chessProgram[n];
757     cps->host = appData.host[n];
758     cps->dir = appData.directory[n];
759     cps->initString = appData.engInitString[n];
760     cps->computerString = appData.computerString[n];
761     cps->useSigint  = TRUE;
762     cps->useSigterm = TRUE;
763     cps->reuse = appData.reuse[n];
764     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
765     cps->useSetboard = FALSE;
766     cps->useSAN = FALSE;
767     cps->usePing = FALSE;
768     cps->lastPing = 0;
769     cps->lastPong = 0;
770     cps->usePlayother = FALSE;
771     cps->useColors = TRUE;
772     cps->useUsermove = FALSE;
773     cps->sendICS = FALSE;
774     cps->sendName = appData.icsActive;
775     cps->sdKludge = FALSE;
776     cps->stKludge = FALSE;
777     TidyProgramName(cps->program, cps->host, cps->tidy);
778     cps->matchWins = 0;
779     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
780     cps->analysisSupport = 2; /* detect */
781     cps->analyzing = FALSE;
782     cps->initDone = FALSE;
783
784     /* New features added by Tord: */
785     cps->useFEN960 = FALSE;
786     cps->useOOCastle = TRUE;
787     /* End of new features added by Tord. */
788     cps->fenOverride  = appData.fenOverride[n];
789
790     /* [HGM] time odds: set factor for each machine */
791     cps->timeOdds  = appData.timeOdds[n];
792
793     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
794     cps->accumulateTC = appData.accumulateTC[n];
795     cps->maxNrOfSessions = 1;
796
797     /* [HGM] debug */
798     cps->debug = FALSE;
799     cps->supportsNPS = UNKNOWN;
800
801     /* [HGM] options */
802     cps->optionSettings  = appData.engOptions[n];
803
804     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
805     cps->isUCI = appData.isUCI[n]; /* [AS] */
806     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
807
808     if (appData.protocolVersion[n] > PROTOVER
809         || appData.protocolVersion[n] < 1)
810       {
811         char buf[MSG_SIZ];
812         int len;
813
814         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
815                        appData.protocolVersion[n]);
816         if( (len > MSG_SIZ) && appData.debugMode )
817           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
818
819         DisplayFatalError(buf, 0, 2);
820       }
821     else
822       {
823         cps->protocolVersion = appData.protocolVersion[n];
824       }
825
826     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
827 }
828
829 ChessProgramState *savCps;
830
831 void
832 LoadEngine()
833 {
834     int i;
835     if(WaitForEngine(savCps, LoadEngine)) return;
836     CommonEngineInit(); // recalculate time odds
837     if(gameInfo.variant != StringToVariant(appData.variant)) {
838         // we changed variant when loading the engine; this forces us to reset
839         Reset(TRUE, savCps != &first);
840         EditGameEvent(); // for consistency with other path, as Reset changes mode
841     }
842     InitChessProgram(savCps, FALSE);
843     SendToProgram("force\n", savCps);
844     DisplayMessage("", "");
845     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
846     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
847     ThawUI();
848     SetGNUMode();
849 }
850
851 void
852 ReplaceEngine(ChessProgramState *cps, int n)
853 {
854     EditGameEvent();
855     UnloadEngine(cps);
856     appData.noChessProgram = FALSE;
857     appData.clockMode = TRUE;
858     InitEngine(cps, n);
859     if(n) return; // only startup first engine immediately; second can wait
860     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
861     LoadEngine();
862 }
863
864 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
865 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
866
867 static char resetOptions[] = 
868         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
869         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
870
871 void
872 Load(ChessProgramState *cps, int i)
873 {
874     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
875     if(engineLine[0]) { // an engine was selected from the combo box
876         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
877         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
878         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
879         ParseArgsFromString(buf);
880         SwapEngines(i);
881         ReplaceEngine(cps, i);
882         return;
883     }
884     p = engineName;
885     while(q = strchr(p, SLASH)) p = q+1;
886     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
887     if(engineDir[0] != NULLCHAR)
888         appData.directory[i] = engineDir;
889     else if(p != engineName) { // derive directory from engine path, when not given
890         p[-1] = 0;
891         appData.directory[i] = strdup(engineName);
892         p[-1] = SLASH;
893     } else appData.directory[i] = ".";
894     if(params[0]) {
895         snprintf(command, MSG_SIZ, "%s %s", p, params);
896         p = command;
897     }
898     appData.chessProgram[i] = strdup(p);
899     appData.isUCI[i] = isUCI;
900     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
901     appData.hasOwnBookUCI[i] = hasBook;
902     if(!nickName[0]) useNick = FALSE;
903     if(useNick) ASSIGN(appData.pgnName[i], nickName);
904     if(addToList) {
905         int len;
906         q = firstChessProgramNames;
907         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
908         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
909                         useNick ? " -fn \"" : "",
910                         useNick ? nickName : "",
911                         useNick ? "\"" : "",
912                         v1 ? " -firstProtocolVersion 1" : "",
913                         hasBook ? "" : " -fNoOwnBookUCI",
914                         isUCI ? " -fUCI" : "",
915                         storeVariant ? " -variant " : "",
916                         storeVariant ? VariantName(gameInfo.variant) : "");
917         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
918         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
919         if(q)   free(q);
920     }
921     ReplaceEngine(cps, i);
922 }
923
924 void
925 InitTimeControls()
926 {
927     int matched, min, sec;
928     /*
929      * Parse timeControl resource
930      */
931     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
932                           appData.movesPerSession)) {
933         char buf[MSG_SIZ];
934         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
935         DisplayFatalError(buf, 0, 2);
936     }
937
938     /*
939      * Parse searchTime resource
940      */
941     if (*appData.searchTime != NULLCHAR) {
942         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
943         if (matched == 1) {
944             searchTime = min * 60;
945         } else if (matched == 2) {
946             searchTime = min * 60 + sec;
947         } else {
948             char buf[MSG_SIZ];
949             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
950             DisplayFatalError(buf, 0, 2);
951         }
952     }
953 }
954
955 void
956 InitBackEnd1()
957 {
958
959     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
960     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
961
962     GetTimeMark(&programStartTime);
963     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
964     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
965
966     ClearProgramStats();
967     programStats.ok_to_send = 1;
968     programStats.seen_stat = 0;
969
970     /*
971      * Initialize game list
972      */
973     ListNew(&gameList);
974
975
976     /*
977      * Internet chess server status
978      */
979     if (appData.icsActive) {
980         appData.matchMode = FALSE;
981         appData.matchGames = 0;
982 #if ZIPPY
983         appData.noChessProgram = !appData.zippyPlay;
984 #else
985         appData.zippyPlay = FALSE;
986         appData.zippyTalk = FALSE;
987         appData.noChessProgram = TRUE;
988 #endif
989         if (*appData.icsHelper != NULLCHAR) {
990             appData.useTelnet = TRUE;
991             appData.telnetProgram = appData.icsHelper;
992         }
993     } else {
994         appData.zippyTalk = appData.zippyPlay = FALSE;
995     }
996
997     /* [AS] Initialize pv info list [HGM] and game state */
998     {
999         int i, j;
1000
1001         for( i=0; i<=framePtr; i++ ) {
1002             pvInfoList[i].depth = -1;
1003             boards[i][EP_STATUS] = EP_NONE;
1004             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1005         }
1006     }
1007
1008     InitTimeControls();
1009
1010     /* [AS] Adjudication threshold */
1011     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1012
1013     InitEngine(&first, 0);
1014     InitEngine(&second, 1);
1015     CommonEngineInit();
1016
1017     if (appData.icsActive) {
1018         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1019     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1020         appData.clockMode = FALSE;
1021         first.sendTime = second.sendTime = 0;
1022     }
1023
1024 #if ZIPPY
1025     /* Override some settings from environment variables, for backward
1026        compatibility.  Unfortunately it's not feasible to have the env
1027        vars just set defaults, at least in xboard.  Ugh.
1028     */
1029     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1030       ZippyInit();
1031     }
1032 #endif
1033
1034     if (!appData.icsActive) {
1035       char buf[MSG_SIZ];
1036       int len;
1037
1038       /* Check for variants that are supported only in ICS mode,
1039          or not at all.  Some that are accepted here nevertheless
1040          have bugs; see comments below.
1041       */
1042       VariantClass variant = StringToVariant(appData.variant);
1043       switch (variant) {
1044       case VariantBughouse:     /* need four players and two boards */
1045       case VariantKriegspiel:   /* need to hide pieces and move details */
1046         /* case VariantFischeRandom: (Fabien: moved below) */
1047         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1048         if( (len > MSG_SIZ) && appData.debugMode )
1049           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1050
1051         DisplayFatalError(buf, 0, 2);
1052         return;
1053
1054       case VariantUnknown:
1055       case VariantLoadable:
1056       case Variant29:
1057       case Variant30:
1058       case Variant31:
1059       case Variant32:
1060       case Variant33:
1061       case Variant34:
1062       case Variant35:
1063       case Variant36:
1064       default:
1065         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1066         if( (len > MSG_SIZ) && appData.debugMode )
1067           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1068
1069         DisplayFatalError(buf, 0, 2);
1070         return;
1071
1072       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1073       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1074       case VariantGothic:     /* [HGM] should work */
1075       case VariantCapablanca: /* [HGM] should work */
1076       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1077       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1078       case VariantKnightmate: /* [HGM] should work */
1079       case VariantCylinder:   /* [HGM] untested */
1080       case VariantFalcon:     /* [HGM] untested */
1081       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1082                                  offboard interposition not understood */
1083       case VariantNormal:     /* definitely works! */
1084       case VariantWildCastle: /* pieces not automatically shuffled */
1085       case VariantNoCastle:   /* pieces not automatically shuffled */
1086       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1087       case VariantLosers:     /* should work except for win condition,
1088                                  and doesn't know captures are mandatory */
1089       case VariantSuicide:    /* should work except for win condition,
1090                                  and doesn't know captures are mandatory */
1091       case VariantGiveaway:   /* should work except for win condition,
1092                                  and doesn't know captures are mandatory */
1093       case VariantTwoKings:   /* should work */
1094       case VariantAtomic:     /* should work except for win condition */
1095       case Variant3Check:     /* should work except for win condition */
1096       case VariantShatranj:   /* should work except for all win conditions */
1097       case VariantMakruk:     /* should work except for daw countdown */
1098       case VariantBerolina:   /* might work if TestLegality is off */
1099       case VariantCapaRandom: /* should work */
1100       case VariantJanus:      /* should work */
1101       case VariantSuper:      /* experimental */
1102       case VariantGreat:      /* experimental, requires legality testing to be off */
1103       case VariantSChess:     /* S-Chess, should work */
1104       case VariantSpartan:    /* should work */
1105         break;
1106       }
1107     }
1108
1109 }
1110
1111 int NextIntegerFromString( char ** str, long * value )
1112 {
1113     int result = -1;
1114     char * s = *str;
1115
1116     while( *s == ' ' || *s == '\t' ) {
1117         s++;
1118     }
1119
1120     *value = 0;
1121
1122     if( *s >= '0' && *s <= '9' ) {
1123         while( *s >= '0' && *s <= '9' ) {
1124             *value = *value * 10 + (*s - '0');
1125             s++;
1126         }
1127
1128         result = 0;
1129     }
1130
1131     *str = s;
1132
1133     return result;
1134 }
1135
1136 int NextTimeControlFromString( char ** str, long * value )
1137 {
1138     long temp;
1139     int result = NextIntegerFromString( str, &temp );
1140
1141     if( result == 0 ) {
1142         *value = temp * 60; /* Minutes */
1143         if( **str == ':' ) {
1144             (*str)++;
1145             result = NextIntegerFromString( str, &temp );
1146             *value += temp; /* Seconds */
1147         }
1148     }
1149
1150     return result;
1151 }
1152
1153 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1154 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1155     int result = -1, type = 0; long temp, temp2;
1156
1157     if(**str != ':') return -1; // old params remain in force!
1158     (*str)++;
1159     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1160     if( NextIntegerFromString( str, &temp ) ) return -1;
1161     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1162
1163     if(**str != '/') {
1164         /* time only: incremental or sudden-death time control */
1165         if(**str == '+') { /* increment follows; read it */
1166             (*str)++;
1167             if(**str == '!') type = *(*str)++; // Bronstein TC
1168             if(result = NextIntegerFromString( str, &temp2)) return -1;
1169             *inc = temp2 * 1000;
1170             if(**str == '.') { // read fraction of increment
1171                 char *start = ++(*str);
1172                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1173                 temp2 *= 1000;
1174                 while(start++ < *str) temp2 /= 10;
1175                 *inc += temp2;
1176             }
1177         } else *inc = 0;
1178         *moves = 0; *tc = temp * 1000; *incType = type;
1179         return 0;
1180     }
1181
1182     (*str)++; /* classical time control */
1183     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1184
1185     if(result == 0) {
1186         *moves = temp;
1187         *tc    = temp2 * 1000;
1188         *inc   = 0;
1189         *incType = type;
1190     }
1191     return result;
1192 }
1193
1194 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1195 {   /* [HGM] get time to add from the multi-session time-control string */
1196     int incType, moves=1; /* kludge to force reading of first session */
1197     long time, increment;
1198     char *s = tcString;
1199
1200     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1201     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1202     do {
1203         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1204         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1205         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1206         if(movenr == -1) return time;    /* last move before new session     */
1207         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1208         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1209         if(!moves) return increment;     /* current session is incremental   */
1210         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1211     } while(movenr >= -1);               /* try again for next session       */
1212
1213     return 0; // no new time quota on this move
1214 }
1215
1216 int
1217 ParseTimeControl(tc, ti, mps)
1218      char *tc;
1219      float ti;
1220      int mps;
1221 {
1222   long tc1;
1223   long tc2;
1224   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1225   int min, sec=0;
1226
1227   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1228   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1229       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1230   if(ti > 0) {
1231
1232     if(mps)
1233       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1234     else 
1235       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1236   } else {
1237     if(mps)
1238       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1239     else 
1240       snprintf(buf, MSG_SIZ, ":%s", mytc);
1241   }
1242   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1243   
1244   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1245     return FALSE;
1246   }
1247
1248   if( *tc == '/' ) {
1249     /* Parse second time control */
1250     tc++;
1251
1252     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1253       return FALSE;
1254     }
1255
1256     if( tc2 == 0 ) {
1257       return FALSE;
1258     }
1259
1260     timeControl_2 = tc2 * 1000;
1261   }
1262   else {
1263     timeControl_2 = 0;
1264   }
1265
1266   if( tc1 == 0 ) {
1267     return FALSE;
1268   }
1269
1270   timeControl = tc1 * 1000;
1271
1272   if (ti >= 0) {
1273     timeIncrement = ti * 1000;  /* convert to ms */
1274     movesPerSession = 0;
1275   } else {
1276     timeIncrement = 0;
1277     movesPerSession = mps;
1278   }
1279   return TRUE;
1280 }
1281
1282 void
1283 InitBackEnd2()
1284 {
1285     if (appData.debugMode) {
1286         fprintf(debugFP, "%s\n", programVersion);
1287     }
1288
1289     set_cont_sequence(appData.wrapContSeq);
1290     if (appData.matchGames > 0) {
1291         appData.matchMode = TRUE;
1292     } else if (appData.matchMode) {
1293         appData.matchGames = 1;
1294     }
1295     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1296         appData.matchGames = appData.sameColorGames;
1297     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1298         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1299         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1300     }
1301     Reset(TRUE, FALSE);
1302     if (appData.noChessProgram || first.protocolVersion == 1) {
1303       InitBackEnd3();
1304     } else {
1305       /* kludge: allow timeout for initial "feature" commands */
1306       FreezeUI();
1307       DisplayMessage("", _("Starting chess program"));
1308       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1309     }
1310 }
1311
1312 int
1313 CalculateIndex(int index, int gameNr)
1314 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1315     int res;
1316     if(index > 0) return index; // fixed nmber
1317     if(index == 0) return 1;
1318     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1319     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1320     return res;
1321 }
1322
1323 int
1324 LoadGameOrPosition(int gameNr)
1325 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1326     if (*appData.loadGameFile != NULLCHAR) {
1327         if (!LoadGameFromFile(appData.loadGameFile,
1328                 CalculateIndex(appData.loadGameIndex, gameNr),
1329                               appData.loadGameFile, FALSE)) {
1330             DisplayFatalError(_("Bad game file"), 0, 1);
1331             return 0;
1332         }
1333     } else if (*appData.loadPositionFile != NULLCHAR) {
1334         if (!LoadPositionFromFile(appData.loadPositionFile,
1335                 CalculateIndex(appData.loadPositionIndex, gameNr),
1336                                   appData.loadPositionFile)) {
1337             DisplayFatalError(_("Bad position file"), 0, 1);
1338             return 0;
1339         }
1340     }
1341     return 1;
1342 }
1343
1344 void
1345 ReserveGame(int gameNr, char resChar)
1346 {
1347     FILE *tf = fopen(appData.tourneyFile, "r+");
1348     char *p, *q, c, buf[MSG_SIZ];
1349     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1350     safeStrCpy(buf, lastMsg, MSG_SIZ);
1351     DisplayMessage(_("Pick new game"), "");
1352     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1353     ParseArgsFromFile(tf);
1354     p = q = appData.results;
1355     if(appData.debugMode) {
1356       char *r = appData.participants;
1357       fprintf(debugFP, "results = '%s'\n", p);
1358       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1359       fprintf(debugFP, "\n");
1360     }
1361     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1362     nextGame = q - p;
1363     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1364     safeStrCpy(q, p, strlen(p) + 2);
1365     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1366     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1367     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1368         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1369         q[nextGame] = '*';
1370     }
1371     fseek(tf, -(strlen(p)+4), SEEK_END);
1372     c = fgetc(tf);
1373     if(c != '"') // depending on DOS or Unix line endings we can be one off
1374          fseek(tf, -(strlen(p)+2), SEEK_END);
1375     else fseek(tf, -(strlen(p)+3), SEEK_END);
1376     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1377     DisplayMessage(buf, "");
1378     free(p); appData.results = q;
1379     if(nextGame <= appData.matchGames && resChar != ' ' &&
1380        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1381         UnloadEngine(&first);  // next game belongs to other pairing;
1382         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1383     }
1384 }
1385
1386 void
1387 MatchEvent(int mode)
1388 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1389         int dummy;
1390         if(matchMode) { // already in match mode: switch it off
1391             abortMatch = TRUE;
1392             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1393             ModeHighlight(); // kludgey way to remove checkmark...
1394             return;
1395         }
1396 //      if(gameMode != BeginningOfGame) {
1397 //          DisplayError(_("You can only start a match from the initial position."), 0);
1398 //          return;
1399 //      }
1400         abortMatch = FALSE;
1401         appData.matchGames = appData.defaultMatchGames;
1402         /* Set up machine vs. machine match */
1403         nextGame = 0;
1404         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1405         if(appData.tourneyFile[0]) {
1406             ReserveGame(-1, 0);
1407             if(nextGame > appData.matchGames) {
1408                 char buf[MSG_SIZ];
1409                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1410                 DisplayError(buf, 0);
1411                 appData.tourneyFile[0] = 0;
1412                 return;
1413             }
1414         } else
1415         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1416             DisplayFatalError(_("Can't have a match with no chess programs"),
1417                               0, 2);
1418             return;
1419         }
1420         matchMode = mode;
1421         matchGame = roundNr = 1;
1422         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1423         NextMatchGame();
1424 }
1425
1426 void
1427 InitBackEnd3 P((void))
1428 {
1429     GameMode initialMode;
1430     char buf[MSG_SIZ];
1431     int err, len;
1432
1433     InitChessProgram(&first, startedFromSetupPosition);
1434
1435     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1436         free(programVersion);
1437         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1438         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1439     }
1440
1441     if (appData.icsActive) {
1442 #ifdef WIN32
1443         /* [DM] Make a console window if needed [HGM] merged ifs */
1444         ConsoleCreate();
1445 #endif
1446         err = establish();
1447         if (err != 0)
1448           {
1449             if (*appData.icsCommPort != NULLCHAR)
1450               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1451                              appData.icsCommPort);
1452             else
1453               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1454                         appData.icsHost, appData.icsPort);
1455
1456             if( (len > MSG_SIZ) && appData.debugMode )
1457               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1458
1459             DisplayFatalError(buf, err, 1);
1460             return;
1461         }
1462         SetICSMode();
1463         telnetISR =
1464           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1465         fromUserISR =
1466           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1467         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1468             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1469     } else if (appData.noChessProgram) {
1470         SetNCPMode();
1471     } else {
1472         SetGNUMode();
1473     }
1474
1475     if (*appData.cmailGameName != NULLCHAR) {
1476         SetCmailMode();
1477         OpenLoopback(&cmailPR);
1478         cmailISR =
1479           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1480     }
1481
1482     ThawUI();
1483     DisplayMessage("", "");
1484     if (StrCaseCmp(appData.initialMode, "") == 0) {
1485       initialMode = BeginningOfGame;
1486       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1487         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1488         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1489         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1490         ModeHighlight();
1491       }
1492     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1493       initialMode = TwoMachinesPlay;
1494     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1495       initialMode = AnalyzeFile;
1496     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1497       initialMode = AnalyzeMode;
1498     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1499       initialMode = MachinePlaysWhite;
1500     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1501       initialMode = MachinePlaysBlack;
1502     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1503       initialMode = EditGame;
1504     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1505       initialMode = EditPosition;
1506     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1507       initialMode = Training;
1508     } else {
1509       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1510       if( (len > MSG_SIZ) && appData.debugMode )
1511         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1512
1513       DisplayFatalError(buf, 0, 2);
1514       return;
1515     }
1516
1517     if (appData.matchMode) {
1518         if(appData.tourneyFile[0]) { // start tourney from command line
1519             FILE *f;
1520             if(f = fopen(appData.tourneyFile, "r")) {
1521                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1522                 fclose(f);
1523             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1524         }
1525         MatchEvent(TRUE);
1526     } else if (*appData.cmailGameName != NULLCHAR) {
1527         /* Set up cmail mode */
1528         ReloadCmailMsgEvent(TRUE);
1529     } else {
1530         /* Set up other modes */
1531         if (initialMode == AnalyzeFile) {
1532           if (*appData.loadGameFile == NULLCHAR) {
1533             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1534             return;
1535           }
1536         }
1537         if (*appData.loadGameFile != NULLCHAR) {
1538             (void) LoadGameFromFile(appData.loadGameFile,
1539                                     appData.loadGameIndex,
1540                                     appData.loadGameFile, TRUE);
1541         } else if (*appData.loadPositionFile != NULLCHAR) {
1542             (void) LoadPositionFromFile(appData.loadPositionFile,
1543                                         appData.loadPositionIndex,
1544                                         appData.loadPositionFile);
1545             /* [HGM] try to make self-starting even after FEN load */
1546             /* to allow automatic setup of fairy variants with wtm */
1547             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1548                 gameMode = BeginningOfGame;
1549                 setboardSpoiledMachineBlack = 1;
1550             }
1551             /* [HGM] loadPos: make that every new game uses the setup */
1552             /* from file as long as we do not switch variant          */
1553             if(!blackPlaysFirst) {
1554                 startedFromPositionFile = TRUE;
1555                 CopyBoard(filePosition, boards[0]);
1556             }
1557         }
1558         if (initialMode == AnalyzeMode) {
1559           if (appData.noChessProgram) {
1560             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1561             return;
1562           }
1563           if (appData.icsActive) {
1564             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1565             return;
1566           }
1567           AnalyzeModeEvent();
1568         } else if (initialMode == AnalyzeFile) {
1569           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1570           ShowThinkingEvent();
1571           AnalyzeFileEvent();
1572           AnalysisPeriodicEvent(1);
1573         } else if (initialMode == MachinePlaysWhite) {
1574           if (appData.noChessProgram) {
1575             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1576                               0, 2);
1577             return;
1578           }
1579           if (appData.icsActive) {
1580             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1581                               0, 2);
1582             return;
1583           }
1584           MachineWhiteEvent();
1585         } else if (initialMode == MachinePlaysBlack) {
1586           if (appData.noChessProgram) {
1587             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1588                               0, 2);
1589             return;
1590           }
1591           if (appData.icsActive) {
1592             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1593                               0, 2);
1594             return;
1595           }
1596           MachineBlackEvent();
1597         } else if (initialMode == TwoMachinesPlay) {
1598           if (appData.noChessProgram) {
1599             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1600                               0, 2);
1601             return;
1602           }
1603           if (appData.icsActive) {
1604             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1605                               0, 2);
1606             return;
1607           }
1608           TwoMachinesEvent();
1609         } else if (initialMode == EditGame) {
1610           EditGameEvent();
1611         } else if (initialMode == EditPosition) {
1612           EditPositionEvent();
1613         } else if (initialMode == Training) {
1614           if (*appData.loadGameFile == NULLCHAR) {
1615             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1616             return;
1617           }
1618           TrainingEvent();
1619         }
1620     }
1621 }
1622
1623 /*
1624  * Establish will establish a contact to a remote host.port.
1625  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1626  *  used to talk to the host.
1627  * Returns 0 if okay, error code if not.
1628  */
1629 int
1630 establish()
1631 {
1632     char buf[MSG_SIZ];
1633
1634     if (*appData.icsCommPort != NULLCHAR) {
1635         /* Talk to the host through a serial comm port */
1636         return OpenCommPort(appData.icsCommPort, &icsPR);
1637
1638     } else if (*appData.gateway != NULLCHAR) {
1639         if (*appData.remoteShell == NULLCHAR) {
1640             /* Use the rcmd protocol to run telnet program on a gateway host */
1641             snprintf(buf, sizeof(buf), "%s %s %s",
1642                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1643             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1644
1645         } else {
1646             /* Use the rsh program to run telnet program on a gateway host */
1647             if (*appData.remoteUser == NULLCHAR) {
1648                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1649                         appData.gateway, appData.telnetProgram,
1650                         appData.icsHost, appData.icsPort);
1651             } else {
1652                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1653                         appData.remoteShell, appData.gateway,
1654                         appData.remoteUser, appData.telnetProgram,
1655                         appData.icsHost, appData.icsPort);
1656             }
1657             return StartChildProcess(buf, "", &icsPR);
1658
1659         }
1660     } else if (appData.useTelnet) {
1661         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1662
1663     } else {
1664         /* TCP socket interface differs somewhat between
1665            Unix and NT; handle details in the front end.
1666            */
1667         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1668     }
1669 }
1670
1671 void EscapeExpand(char *p, char *q)
1672 {       // [HGM] initstring: routine to shape up string arguments
1673         while(*p++ = *q++) if(p[-1] == '\\')
1674             switch(*q++) {
1675                 case 'n': p[-1] = '\n'; break;
1676                 case 'r': p[-1] = '\r'; break;
1677                 case 't': p[-1] = '\t'; break;
1678                 case '\\': p[-1] = '\\'; break;
1679                 case 0: *p = 0; return;
1680                 default: p[-1] = q[-1]; break;
1681             }
1682 }
1683
1684 void
1685 show_bytes(fp, buf, count)
1686      FILE *fp;
1687      char *buf;
1688      int count;
1689 {
1690     while (count--) {
1691         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1692             fprintf(fp, "\\%03o", *buf & 0xff);
1693         } else {
1694             putc(*buf, fp);
1695         }
1696         buf++;
1697     }
1698     fflush(fp);
1699 }
1700
1701 /* Returns an errno value */
1702 int
1703 OutputMaybeTelnet(pr, message, count, outError)
1704      ProcRef pr;
1705      char *message;
1706      int count;
1707      int *outError;
1708 {
1709     char buf[8192], *p, *q, *buflim;
1710     int left, newcount, outcount;
1711
1712     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1713         *appData.gateway != NULLCHAR) {
1714         if (appData.debugMode) {
1715             fprintf(debugFP, ">ICS: ");
1716             show_bytes(debugFP, message, count);
1717             fprintf(debugFP, "\n");
1718         }
1719         return OutputToProcess(pr, message, count, outError);
1720     }
1721
1722     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1723     p = message;
1724     q = buf;
1725     left = count;
1726     newcount = 0;
1727     while (left) {
1728         if (q >= buflim) {
1729             if (appData.debugMode) {
1730                 fprintf(debugFP, ">ICS: ");
1731                 show_bytes(debugFP, buf, newcount);
1732                 fprintf(debugFP, "\n");
1733             }
1734             outcount = OutputToProcess(pr, buf, newcount, outError);
1735             if (outcount < newcount) return -1; /* to be sure */
1736             q = buf;
1737             newcount = 0;
1738         }
1739         if (*p == '\n') {
1740             *q++ = '\r';
1741             newcount++;
1742         } else if (((unsigned char) *p) == TN_IAC) {
1743             *q++ = (char) TN_IAC;
1744             newcount ++;
1745         }
1746         *q++ = *p++;
1747         newcount++;
1748         left--;
1749     }
1750     if (appData.debugMode) {
1751         fprintf(debugFP, ">ICS: ");
1752         show_bytes(debugFP, buf, newcount);
1753         fprintf(debugFP, "\n");
1754     }
1755     outcount = OutputToProcess(pr, buf, newcount, outError);
1756     if (outcount < newcount) return -1; /* to be sure */
1757     return count;
1758 }
1759
1760 void
1761 read_from_player(isr, closure, message, count, error)
1762      InputSourceRef isr;
1763      VOIDSTAR closure;
1764      char *message;
1765      int count;
1766      int error;
1767 {
1768     int outError, outCount;
1769     static int gotEof = 0;
1770
1771     /* Pass data read from player on to ICS */
1772     if (count > 0) {
1773         gotEof = 0;
1774         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1775         if (outCount < count) {
1776             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1777         }
1778     } else if (count < 0) {
1779         RemoveInputSource(isr);
1780         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1781     } else if (gotEof++ > 0) {
1782         RemoveInputSource(isr);
1783         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1784     }
1785 }
1786
1787 void
1788 KeepAlive()
1789 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1790     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1791     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1792     SendToICS("date\n");
1793     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1794 }
1795
1796 /* added routine for printf style output to ics */
1797 void ics_printf(char *format, ...)
1798 {
1799     char buffer[MSG_SIZ];
1800     va_list args;
1801
1802     va_start(args, format);
1803     vsnprintf(buffer, sizeof(buffer), format, args);
1804     buffer[sizeof(buffer)-1] = '\0';
1805     SendToICS(buffer);
1806     va_end(args);
1807 }
1808
1809 void
1810 SendToICS(s)
1811      char *s;
1812 {
1813     int count, outCount, outError;
1814
1815     if (icsPR == NULL) return;
1816
1817     count = strlen(s);
1818     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1819     if (outCount < count) {
1820         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1821     }
1822 }
1823
1824 /* This is used for sending logon scripts to the ICS. Sending
1825    without a delay causes problems when using timestamp on ICC
1826    (at least on my machine). */
1827 void
1828 SendToICSDelayed(s,msdelay)
1829      char *s;
1830      long msdelay;
1831 {
1832     int count, outCount, outError;
1833
1834     if (icsPR == NULL) return;
1835
1836     count = strlen(s);
1837     if (appData.debugMode) {
1838         fprintf(debugFP, ">ICS: ");
1839         show_bytes(debugFP, s, count);
1840         fprintf(debugFP, "\n");
1841     }
1842     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1843                                       msdelay);
1844     if (outCount < count) {
1845         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1846     }
1847 }
1848
1849
1850 /* Remove all highlighting escape sequences in s
1851    Also deletes any suffix starting with '('
1852    */
1853 char *
1854 StripHighlightAndTitle(s)
1855      char *s;
1856 {
1857     static char retbuf[MSG_SIZ];
1858     char *p = retbuf;
1859
1860     while (*s != NULLCHAR) {
1861         while (*s == '\033') {
1862             while (*s != NULLCHAR && !isalpha(*s)) s++;
1863             if (*s != NULLCHAR) s++;
1864         }
1865         while (*s != NULLCHAR && *s != '\033') {
1866             if (*s == '(' || *s == '[') {
1867                 *p = NULLCHAR;
1868                 return retbuf;
1869             }
1870             *p++ = *s++;
1871         }
1872     }
1873     *p = NULLCHAR;
1874     return retbuf;
1875 }
1876
1877 /* Remove all highlighting escape sequences in s */
1878 char *
1879 StripHighlight(s)
1880      char *s;
1881 {
1882     static char retbuf[MSG_SIZ];
1883     char *p = retbuf;
1884
1885     while (*s != NULLCHAR) {
1886         while (*s == '\033') {
1887             while (*s != NULLCHAR && !isalpha(*s)) s++;
1888             if (*s != NULLCHAR) s++;
1889         }
1890         while (*s != NULLCHAR && *s != '\033') {
1891             *p++ = *s++;
1892         }
1893     }
1894     *p = NULLCHAR;
1895     return retbuf;
1896 }
1897
1898 char *variantNames[] = VARIANT_NAMES;
1899 char *
1900 VariantName(v)
1901      VariantClass v;
1902 {
1903     return variantNames[v];
1904 }
1905
1906
1907 /* Identify a variant from the strings the chess servers use or the
1908    PGN Variant tag names we use. */
1909 VariantClass
1910 StringToVariant(e)
1911      char *e;
1912 {
1913     char *p;
1914     int wnum = -1;
1915     VariantClass v = VariantNormal;
1916     int i, found = FALSE;
1917     char buf[MSG_SIZ];
1918     int len;
1919
1920     if (!e) return v;
1921
1922     /* [HGM] skip over optional board-size prefixes */
1923     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1924         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1925         while( *e++ != '_');
1926     }
1927
1928     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1929         v = VariantNormal;
1930         found = TRUE;
1931     } else
1932     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1933       if (StrCaseStr(e, variantNames[i])) {
1934         v = (VariantClass) i;
1935         found = TRUE;
1936         break;
1937       }
1938     }
1939
1940     if (!found) {
1941       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1942           || StrCaseStr(e, "wild/fr")
1943           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1944         v = VariantFischeRandom;
1945       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1946                  (i = 1, p = StrCaseStr(e, "w"))) {
1947         p += i;
1948         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1949         if (isdigit(*p)) {
1950           wnum = atoi(p);
1951         } else {
1952           wnum = -1;
1953         }
1954         switch (wnum) {
1955         case 0: /* FICS only, actually */
1956         case 1:
1957           /* Castling legal even if K starts on d-file */
1958           v = VariantWildCastle;
1959           break;
1960         case 2:
1961         case 3:
1962         case 4:
1963           /* Castling illegal even if K & R happen to start in
1964              normal positions. */
1965           v = VariantNoCastle;
1966           break;
1967         case 5:
1968         case 7:
1969         case 8:
1970         case 10:
1971         case 11:
1972         case 12:
1973         case 13:
1974         case 14:
1975         case 15:
1976         case 18:
1977         case 19:
1978           /* Castling legal iff K & R start in normal positions */
1979           v = VariantNormal;
1980           break;
1981         case 6:
1982         case 20:
1983         case 21:
1984           /* Special wilds for position setup; unclear what to do here */
1985           v = VariantLoadable;
1986           break;
1987         case 9:
1988           /* Bizarre ICC game */
1989           v = VariantTwoKings;
1990           break;
1991         case 16:
1992           v = VariantKriegspiel;
1993           break;
1994         case 17:
1995           v = VariantLosers;
1996           break;
1997         case 22:
1998           v = VariantFischeRandom;
1999           break;
2000         case 23:
2001           v = VariantCrazyhouse;
2002           break;
2003         case 24:
2004           v = VariantBughouse;
2005           break;
2006         case 25:
2007           v = Variant3Check;
2008           break;
2009         case 26:
2010           /* Not quite the same as FICS suicide! */
2011           v = VariantGiveaway;
2012           break;
2013         case 27:
2014           v = VariantAtomic;
2015           break;
2016         case 28:
2017           v = VariantShatranj;
2018           break;
2019
2020         /* Temporary names for future ICC types.  The name *will* change in
2021            the next xboard/WinBoard release after ICC defines it. */
2022         case 29:
2023           v = Variant29;
2024           break;
2025         case 30:
2026           v = Variant30;
2027           break;
2028         case 31:
2029           v = Variant31;
2030           break;
2031         case 32:
2032           v = Variant32;
2033           break;
2034         case 33:
2035           v = Variant33;
2036           break;
2037         case 34:
2038           v = Variant34;
2039           break;
2040         case 35:
2041           v = Variant35;
2042           break;
2043         case 36:
2044           v = Variant36;
2045           break;
2046         case 37:
2047           v = VariantShogi;
2048           break;
2049         case 38:
2050           v = VariantXiangqi;
2051           break;
2052         case 39:
2053           v = VariantCourier;
2054           break;
2055         case 40:
2056           v = VariantGothic;
2057           break;
2058         case 41:
2059           v = VariantCapablanca;
2060           break;
2061         case 42:
2062           v = VariantKnightmate;
2063           break;
2064         case 43:
2065           v = VariantFairy;
2066           break;
2067         case 44:
2068           v = VariantCylinder;
2069           break;
2070         case 45:
2071           v = VariantFalcon;
2072           break;
2073         case 46:
2074           v = VariantCapaRandom;
2075           break;
2076         case 47:
2077           v = VariantBerolina;
2078           break;
2079         case 48:
2080           v = VariantJanus;
2081           break;
2082         case 49:
2083           v = VariantSuper;
2084           break;
2085         case 50:
2086           v = VariantGreat;
2087           break;
2088         case -1:
2089           /* Found "wild" or "w" in the string but no number;
2090              must assume it's normal chess. */
2091           v = VariantNormal;
2092           break;
2093         default:
2094           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2095           if( (len > MSG_SIZ) && appData.debugMode )
2096             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2097
2098           DisplayError(buf, 0);
2099           v = VariantUnknown;
2100           break;
2101         }
2102       }
2103     }
2104     if (appData.debugMode) {
2105       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2106               e, wnum, VariantName(v));
2107     }
2108     return v;
2109 }
2110
2111 static int leftover_start = 0, leftover_len = 0;
2112 char star_match[STAR_MATCH_N][MSG_SIZ];
2113
2114 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2115    advance *index beyond it, and set leftover_start to the new value of
2116    *index; else return FALSE.  If pattern contains the character '*', it
2117    matches any sequence of characters not containing '\r', '\n', or the
2118    character following the '*' (if any), and the matched sequence(s) are
2119    copied into star_match.
2120    */
2121 int
2122 looking_at(buf, index, pattern)
2123      char *buf;
2124      int *index;
2125      char *pattern;
2126 {
2127     char *bufp = &buf[*index], *patternp = pattern;
2128     int star_count = 0;
2129     char *matchp = star_match[0];
2130
2131     for (;;) {
2132         if (*patternp == NULLCHAR) {
2133             *index = leftover_start = bufp - buf;
2134             *matchp = NULLCHAR;
2135             return TRUE;
2136         }
2137         if (*bufp == NULLCHAR) return FALSE;
2138         if (*patternp == '*') {
2139             if (*bufp == *(patternp + 1)) {
2140                 *matchp = NULLCHAR;
2141                 matchp = star_match[++star_count];
2142                 patternp += 2;
2143                 bufp++;
2144                 continue;
2145             } else if (*bufp == '\n' || *bufp == '\r') {
2146                 patternp++;
2147                 if (*patternp == NULLCHAR)
2148                   continue;
2149                 else
2150                   return FALSE;
2151             } else {
2152                 *matchp++ = *bufp++;
2153                 continue;
2154             }
2155         }
2156         if (*patternp != *bufp) return FALSE;
2157         patternp++;
2158         bufp++;
2159     }
2160 }
2161
2162 void
2163 SendToPlayer(data, length)
2164      char *data;
2165      int length;
2166 {
2167     int error, outCount;
2168     outCount = OutputToProcess(NoProc, data, length, &error);
2169     if (outCount < length) {
2170         DisplayFatalError(_("Error writing to display"), error, 1);
2171     }
2172 }
2173
2174 void
2175 PackHolding(packed, holding)
2176      char packed[];
2177      char *holding;
2178 {
2179     char *p = holding;
2180     char *q = packed;
2181     int runlength = 0;
2182     int curr = 9999;
2183     do {
2184         if (*p == curr) {
2185             runlength++;
2186         } else {
2187             switch (runlength) {
2188               case 0:
2189                 break;
2190               case 1:
2191                 *q++ = curr;
2192                 break;
2193               case 2:
2194                 *q++ = curr;
2195                 *q++ = curr;
2196                 break;
2197               default:
2198                 sprintf(q, "%d", runlength);
2199                 while (*q) q++;
2200                 *q++ = curr;
2201                 break;
2202             }
2203             runlength = 1;
2204             curr = *p;
2205         }
2206     } while (*p++);
2207     *q = NULLCHAR;
2208 }
2209
2210 /* Telnet protocol requests from the front end */
2211 void
2212 TelnetRequest(ddww, option)
2213      unsigned char ddww, option;
2214 {
2215     unsigned char msg[3];
2216     int outCount, outError;
2217
2218     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2219
2220     if (appData.debugMode) {
2221         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2222         switch (ddww) {
2223           case TN_DO:
2224             ddwwStr = "DO";
2225             break;
2226           case TN_DONT:
2227             ddwwStr = "DONT";
2228             break;
2229           case TN_WILL:
2230             ddwwStr = "WILL";
2231             break;
2232           case TN_WONT:
2233             ddwwStr = "WONT";
2234             break;
2235           default:
2236             ddwwStr = buf1;
2237             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2238             break;
2239         }
2240         switch (option) {
2241           case TN_ECHO:
2242             optionStr = "ECHO";
2243             break;
2244           default:
2245             optionStr = buf2;
2246             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2247             break;
2248         }
2249         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2250     }
2251     msg[0] = TN_IAC;
2252     msg[1] = ddww;
2253     msg[2] = option;
2254     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2255     if (outCount < 3) {
2256         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2257     }
2258 }
2259
2260 void
2261 DoEcho()
2262 {
2263     if (!appData.icsActive) return;
2264     TelnetRequest(TN_DO, TN_ECHO);
2265 }
2266
2267 void
2268 DontEcho()
2269 {
2270     if (!appData.icsActive) return;
2271     TelnetRequest(TN_DONT, TN_ECHO);
2272 }
2273
2274 void
2275 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2276 {
2277     /* put the holdings sent to us by the server on the board holdings area */
2278     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2279     char p;
2280     ChessSquare piece;
2281
2282     if(gameInfo.holdingsWidth < 2)  return;
2283     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2284         return; // prevent overwriting by pre-board holdings
2285
2286     if( (int)lowestPiece >= BlackPawn ) {
2287         holdingsColumn = 0;
2288         countsColumn = 1;
2289         holdingsStartRow = BOARD_HEIGHT-1;
2290         direction = -1;
2291     } else {
2292         holdingsColumn = BOARD_WIDTH-1;
2293         countsColumn = BOARD_WIDTH-2;
2294         holdingsStartRow = 0;
2295         direction = 1;
2296     }
2297
2298     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2299         board[i][holdingsColumn] = EmptySquare;
2300         board[i][countsColumn]   = (ChessSquare) 0;
2301     }
2302     while( (p=*holdings++) != NULLCHAR ) {
2303         piece = CharToPiece( ToUpper(p) );
2304         if(piece == EmptySquare) continue;
2305         /*j = (int) piece - (int) WhitePawn;*/
2306         j = PieceToNumber(piece);
2307         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2308         if(j < 0) continue;               /* should not happen */
2309         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2310         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2311         board[holdingsStartRow+j*direction][countsColumn]++;
2312     }
2313 }
2314
2315
2316 void
2317 VariantSwitch(Board board, VariantClass newVariant)
2318 {
2319    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2320    static Board oldBoard;
2321
2322    startedFromPositionFile = FALSE;
2323    if(gameInfo.variant == newVariant) return;
2324
2325    /* [HGM] This routine is called each time an assignment is made to
2326     * gameInfo.variant during a game, to make sure the board sizes
2327     * are set to match the new variant. If that means adding or deleting
2328     * holdings, we shift the playing board accordingly
2329     * This kludge is needed because in ICS observe mode, we get boards
2330     * of an ongoing game without knowing the variant, and learn about the
2331     * latter only later. This can be because of the move list we requested,
2332     * in which case the game history is refilled from the beginning anyway,
2333     * but also when receiving holdings of a crazyhouse game. In the latter
2334     * case we want to add those holdings to the already received position.
2335     */
2336
2337
2338    if (appData.debugMode) {
2339      fprintf(debugFP, "Switch board from %s to %s\n",
2340              VariantName(gameInfo.variant), VariantName(newVariant));
2341      setbuf(debugFP, NULL);
2342    }
2343    shuffleOpenings = 0;       /* [HGM] shuffle */
2344    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2345    switch(newVariant)
2346      {
2347      case VariantShogi:
2348        newWidth = 9;  newHeight = 9;
2349        gameInfo.holdingsSize = 7;
2350      case VariantBughouse:
2351      case VariantCrazyhouse:
2352        newHoldingsWidth = 2; break;
2353      case VariantGreat:
2354        newWidth = 10;
2355      case VariantSuper:
2356        newHoldingsWidth = 2;
2357        gameInfo.holdingsSize = 8;
2358        break;
2359      case VariantGothic:
2360      case VariantCapablanca:
2361      case VariantCapaRandom:
2362        newWidth = 10;
2363      default:
2364        newHoldingsWidth = gameInfo.holdingsSize = 0;
2365      };
2366
2367    if(newWidth  != gameInfo.boardWidth  ||
2368       newHeight != gameInfo.boardHeight ||
2369       newHoldingsWidth != gameInfo.holdingsWidth ) {
2370
2371      /* shift position to new playing area, if needed */
2372      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2373        for(i=0; i<BOARD_HEIGHT; i++)
2374          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2375            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2376              board[i][j];
2377        for(i=0; i<newHeight; i++) {
2378          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2379          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2380        }
2381      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2382        for(i=0; i<BOARD_HEIGHT; i++)
2383          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2384            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2385              board[i][j];
2386      }
2387      gameInfo.boardWidth  = newWidth;
2388      gameInfo.boardHeight = newHeight;
2389      gameInfo.holdingsWidth = newHoldingsWidth;
2390      gameInfo.variant = newVariant;
2391      InitDrawingSizes(-2, 0);
2392    } else gameInfo.variant = newVariant;
2393    CopyBoard(oldBoard, board);   // remember correctly formatted board
2394      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2395    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2396 }
2397
2398 static int loggedOn = FALSE;
2399
2400 /*-- Game start info cache: --*/
2401 int gs_gamenum;
2402 char gs_kind[MSG_SIZ];
2403 static char player1Name[128] = "";
2404 static char player2Name[128] = "";
2405 static char cont_seq[] = "\n\\   ";
2406 static int player1Rating = -1;
2407 static int player2Rating = -1;
2408 /*----------------------------*/
2409
2410 ColorClass curColor = ColorNormal;
2411 int suppressKibitz = 0;
2412
2413 // [HGM] seekgraph
2414 Boolean soughtPending = FALSE;
2415 Boolean seekGraphUp;
2416 #define MAX_SEEK_ADS 200
2417 #define SQUARE 0x80
2418 char *seekAdList[MAX_SEEK_ADS];
2419 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2420 float tcList[MAX_SEEK_ADS];
2421 char colorList[MAX_SEEK_ADS];
2422 int nrOfSeekAds = 0;
2423 int minRating = 1010, maxRating = 2800;
2424 int hMargin = 10, vMargin = 20, h, w;
2425 extern int squareSize, lineGap;
2426
2427 void
2428 PlotSeekAd(int i)
2429 {
2430         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2431         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2432         if(r < minRating+100 && r >=0 ) r = minRating+100;
2433         if(r > maxRating) r = maxRating;
2434         if(tc < 1.) tc = 1.;
2435         if(tc > 95.) tc = 95.;
2436         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2437         y = ((double)r - minRating)/(maxRating - minRating)
2438             * (h-vMargin-squareSize/8-1) + vMargin;
2439         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2440         if(strstr(seekAdList[i], " u ")) color = 1;
2441         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2442            !strstr(seekAdList[i], "bullet") &&
2443            !strstr(seekAdList[i], "blitz") &&
2444            !strstr(seekAdList[i], "standard") ) color = 2;
2445         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2446         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2447 }
2448
2449 void
2450 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2451 {
2452         char buf[MSG_SIZ], *ext = "";
2453         VariantClass v = StringToVariant(type);
2454         if(strstr(type, "wild")) {
2455             ext = type + 4; // append wild number
2456             if(v == VariantFischeRandom) type = "chess960"; else
2457             if(v == VariantLoadable) type = "setup"; else
2458             type = VariantName(v);
2459         }
2460         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2461         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2462             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2463             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2464             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2465             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2466             seekNrList[nrOfSeekAds] = nr;
2467             zList[nrOfSeekAds] = 0;
2468             seekAdList[nrOfSeekAds++] = StrSave(buf);
2469             if(plot) PlotSeekAd(nrOfSeekAds-1);
2470         }
2471 }
2472
2473 void
2474 EraseSeekDot(int i)
2475 {
2476     int x = xList[i], y = yList[i], d=squareSize/4, k;
2477     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2478     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2479     // now replot every dot that overlapped
2480     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2481         int xx = xList[k], yy = yList[k];
2482         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2483             DrawSeekDot(xx, yy, colorList[k]);
2484     }
2485 }
2486
2487 void
2488 RemoveSeekAd(int nr)
2489 {
2490         int i;
2491         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2492             EraseSeekDot(i);
2493             if(seekAdList[i]) free(seekAdList[i]);
2494             seekAdList[i] = seekAdList[--nrOfSeekAds];
2495             seekNrList[i] = seekNrList[nrOfSeekAds];
2496             ratingList[i] = ratingList[nrOfSeekAds];
2497             colorList[i]  = colorList[nrOfSeekAds];
2498             tcList[i] = tcList[nrOfSeekAds];
2499             xList[i]  = xList[nrOfSeekAds];
2500             yList[i]  = yList[nrOfSeekAds];
2501             zList[i]  = zList[nrOfSeekAds];
2502             seekAdList[nrOfSeekAds] = NULL;
2503             break;
2504         }
2505 }
2506
2507 Boolean
2508 MatchSoughtLine(char *line)
2509 {
2510     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2511     int nr, base, inc, u=0; char dummy;
2512
2513     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2514        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2515        (u=1) &&
2516        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2517         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2518         // match: compact and save the line
2519         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2520         return TRUE;
2521     }
2522     return FALSE;
2523 }
2524
2525 int
2526 DrawSeekGraph()
2527 {
2528     int i;
2529     if(!seekGraphUp) return FALSE;
2530     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2531     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2532
2533     DrawSeekBackground(0, 0, w, h);
2534     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2535     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2536     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2537         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2538         yy = h-1-yy;
2539         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2540         if(i%500 == 0) {
2541             char buf[MSG_SIZ];
2542             snprintf(buf, MSG_SIZ, "%d", i);
2543             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2544         }
2545     }
2546     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2547     for(i=1; i<100; i+=(i<10?1:5)) {
2548         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2549         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2550         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2551             char buf[MSG_SIZ];
2552             snprintf(buf, MSG_SIZ, "%d", i);
2553             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2554         }
2555     }
2556     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2557     return TRUE;
2558 }
2559
2560 int SeekGraphClick(ClickType click, int x, int y, int moving)
2561 {
2562     static int lastDown = 0, displayed = 0, lastSecond;
2563     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2564         if(click == Release || moving) return FALSE;
2565         nrOfSeekAds = 0;
2566         soughtPending = TRUE;
2567         SendToICS(ics_prefix);
2568         SendToICS("sought\n"); // should this be "sought all"?
2569     } else { // issue challenge based on clicked ad
2570         int dist = 10000; int i, closest = 0, second = 0;
2571         for(i=0; i<nrOfSeekAds; i++) {
2572             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2573             if(d < dist) { dist = d; closest = i; }
2574             second += (d - zList[i] < 120); // count in-range ads
2575             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2576         }
2577         if(dist < 120) {
2578             char buf[MSG_SIZ];
2579             second = (second > 1);
2580             if(displayed != closest || second != lastSecond) {
2581                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2582                 lastSecond = second; displayed = closest;
2583             }
2584             if(click == Press) {
2585                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2586                 lastDown = closest;
2587                 return TRUE;
2588             } // on press 'hit', only show info
2589             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2590             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2591             SendToICS(ics_prefix);
2592             SendToICS(buf);
2593             return TRUE; // let incoming board of started game pop down the graph
2594         } else if(click == Release) { // release 'miss' is ignored
2595             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2596             if(moving == 2) { // right up-click
2597                 nrOfSeekAds = 0; // refresh graph
2598                 soughtPending = TRUE;
2599                 SendToICS(ics_prefix);
2600                 SendToICS("sought\n"); // should this be "sought all"?
2601             }
2602             return TRUE;
2603         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2604         // press miss or release hit 'pop down' seek graph
2605         seekGraphUp = FALSE;
2606         DrawPosition(TRUE, NULL);
2607     }
2608     return TRUE;
2609 }
2610
2611 void
2612 read_from_ics(isr, closure, data, count, error)
2613      InputSourceRef isr;
2614      VOIDSTAR closure;
2615      char *data;
2616      int count;
2617      int error;
2618 {
2619 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2620 #define STARTED_NONE 0
2621 #define STARTED_MOVES 1
2622 #define STARTED_BOARD 2
2623 #define STARTED_OBSERVE 3
2624 #define STARTED_HOLDINGS 4
2625 #define STARTED_CHATTER 5
2626 #define STARTED_COMMENT 6
2627 #define STARTED_MOVES_NOHIDE 7
2628
2629     static int started = STARTED_NONE;
2630     static char parse[20000];
2631     static int parse_pos = 0;
2632     static char buf[BUF_SIZE + 1];
2633     static int firstTime = TRUE, intfSet = FALSE;
2634     static ColorClass prevColor = ColorNormal;
2635     static int savingComment = FALSE;
2636     static int cmatch = 0; // continuation sequence match
2637     char *bp;
2638     char str[MSG_SIZ];
2639     int i, oldi;
2640     int buf_len;
2641     int next_out;
2642     int tkind;
2643     int backup;    /* [DM] For zippy color lines */
2644     char *p;
2645     char talker[MSG_SIZ]; // [HGM] chat
2646     int channel;
2647
2648     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2649
2650     if (appData.debugMode) {
2651       if (!error) {
2652         fprintf(debugFP, "<ICS: ");
2653         show_bytes(debugFP, data, count);
2654         fprintf(debugFP, "\n");
2655       }
2656     }
2657
2658     if (appData.debugMode) { int f = forwardMostMove;
2659         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2660                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2661                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2662     }
2663     if (count > 0) {
2664         /* If last read ended with a partial line that we couldn't parse,
2665            prepend it to the new read and try again. */
2666         if (leftover_len > 0) {
2667             for (i=0; i<leftover_len; i++)
2668               buf[i] = buf[leftover_start + i];
2669         }
2670
2671     /* copy new characters into the buffer */
2672     bp = buf + leftover_len;
2673     buf_len=leftover_len;
2674     for (i=0; i<count; i++)
2675     {
2676         // ignore these
2677         if (data[i] == '\r')
2678             continue;
2679
2680         // join lines split by ICS?
2681         if (!appData.noJoin)
2682         {
2683             /*
2684                 Joining just consists of finding matches against the
2685                 continuation sequence, and discarding that sequence
2686                 if found instead of copying it.  So, until a match
2687                 fails, there's nothing to do since it might be the
2688                 complete sequence, and thus, something we don't want
2689                 copied.
2690             */
2691             if (data[i] == cont_seq[cmatch])
2692             {
2693                 cmatch++;
2694                 if (cmatch == strlen(cont_seq))
2695                 {
2696                     cmatch = 0; // complete match.  just reset the counter
2697
2698                     /*
2699                         it's possible for the ICS to not include the space
2700                         at the end of the last word, making our [correct]
2701                         join operation fuse two separate words.  the server
2702                         does this when the space occurs at the width setting.
2703                     */
2704                     if (!buf_len || buf[buf_len-1] != ' ')
2705                     {
2706                         *bp++ = ' ';
2707                         buf_len++;
2708                     }
2709                 }
2710                 continue;
2711             }
2712             else if (cmatch)
2713             {
2714                 /*
2715                     match failed, so we have to copy what matched before
2716                     falling through and copying this character.  In reality,
2717                     this will only ever be just the newline character, but
2718                     it doesn't hurt to be precise.
2719                 */
2720                 strncpy(bp, cont_seq, cmatch);
2721                 bp += cmatch;
2722                 buf_len += cmatch;
2723                 cmatch = 0;
2724             }
2725         }
2726
2727         // copy this char
2728         *bp++ = data[i];
2729         buf_len++;
2730     }
2731
2732         buf[buf_len] = NULLCHAR;
2733 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2734         next_out = 0;
2735         leftover_start = 0;
2736
2737         i = 0;
2738         while (i < buf_len) {
2739             /* Deal with part of the TELNET option negotiation
2740                protocol.  We refuse to do anything beyond the
2741                defaults, except that we allow the WILL ECHO option,
2742                which ICS uses to turn off password echoing when we are
2743                directly connected to it.  We reject this option
2744                if localLineEditing mode is on (always on in xboard)
2745                and we are talking to port 23, which might be a real
2746                telnet server that will try to keep WILL ECHO on permanently.
2747              */
2748             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2749                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2750                 unsigned char option;
2751                 oldi = i;
2752                 switch ((unsigned char) buf[++i]) {
2753                   case TN_WILL:
2754                     if (appData.debugMode)
2755                       fprintf(debugFP, "\n<WILL ");
2756                     switch (option = (unsigned char) buf[++i]) {
2757                       case TN_ECHO:
2758                         if (appData.debugMode)
2759                           fprintf(debugFP, "ECHO ");
2760                         /* Reply only if this is a change, according
2761                            to the protocol rules. */
2762                         if (remoteEchoOption) break;
2763                         if (appData.localLineEditing &&
2764                             atoi(appData.icsPort) == TN_PORT) {
2765                             TelnetRequest(TN_DONT, TN_ECHO);
2766                         } else {
2767                             EchoOff();
2768                             TelnetRequest(TN_DO, TN_ECHO);
2769                             remoteEchoOption = TRUE;
2770                         }
2771                         break;
2772                       default:
2773                         if (appData.debugMode)
2774                           fprintf(debugFP, "%d ", option);
2775                         /* Whatever this is, we don't want it. */
2776                         TelnetRequest(TN_DONT, option);
2777                         break;
2778                     }
2779                     break;
2780                   case TN_WONT:
2781                     if (appData.debugMode)
2782                       fprintf(debugFP, "\n<WONT ");
2783                     switch (option = (unsigned char) buf[++i]) {
2784                       case TN_ECHO:
2785                         if (appData.debugMode)
2786                           fprintf(debugFP, "ECHO ");
2787                         /* Reply only if this is a change, according
2788                            to the protocol rules. */
2789                         if (!remoteEchoOption) break;
2790                         EchoOn();
2791                         TelnetRequest(TN_DONT, TN_ECHO);
2792                         remoteEchoOption = FALSE;
2793                         break;
2794                       default:
2795                         if (appData.debugMode)
2796                           fprintf(debugFP, "%d ", (unsigned char) option);
2797                         /* Whatever this is, it must already be turned
2798                            off, because we never agree to turn on
2799                            anything non-default, so according to the
2800                            protocol rules, we don't reply. */
2801                         break;
2802                     }
2803                     break;
2804                   case TN_DO:
2805                     if (appData.debugMode)
2806                       fprintf(debugFP, "\n<DO ");
2807                     switch (option = (unsigned char) buf[++i]) {
2808                       default:
2809                         /* Whatever this is, we refuse to do it. */
2810                         if (appData.debugMode)
2811                           fprintf(debugFP, "%d ", option);
2812                         TelnetRequest(TN_WONT, option);
2813                         break;
2814                     }
2815                     break;
2816                   case TN_DONT:
2817                     if (appData.debugMode)
2818                       fprintf(debugFP, "\n<DONT ");
2819                     switch (option = (unsigned char) buf[++i]) {
2820                       default:
2821                         if (appData.debugMode)
2822                           fprintf(debugFP, "%d ", option);
2823                         /* Whatever this is, we are already not doing
2824                            it, because we never agree to do anything
2825                            non-default, so according to the protocol
2826                            rules, we don't reply. */
2827                         break;
2828                     }
2829                     break;
2830                   case TN_IAC:
2831                     if (appData.debugMode)
2832                       fprintf(debugFP, "\n<IAC ");
2833                     /* Doubled IAC; pass it through */
2834                     i--;
2835                     break;
2836                   default:
2837                     if (appData.debugMode)
2838                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2839                     /* Drop all other telnet commands on the floor */
2840                     break;
2841                 }
2842                 if (oldi > next_out)
2843                   SendToPlayer(&buf[next_out], oldi - next_out);
2844                 if (++i > next_out)
2845                   next_out = i;
2846                 continue;
2847             }
2848
2849             /* OK, this at least will *usually* work */
2850             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2851                 loggedOn = TRUE;
2852             }
2853
2854             if (loggedOn && !intfSet) {
2855                 if (ics_type == ICS_ICC) {
2856                   snprintf(str, MSG_SIZ,
2857                           "/set-quietly interface %s\n/set-quietly style 12\n",
2858                           programVersion);
2859                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2860                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2861                 } else if (ics_type == ICS_CHESSNET) {
2862                   snprintf(str, MSG_SIZ, "/style 12\n");
2863                 } else {
2864                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2865                   strcat(str, programVersion);
2866                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2867                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2868                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2869 #ifdef WIN32
2870                   strcat(str, "$iset nohighlight 1\n");
2871 #endif
2872                   strcat(str, "$iset lock 1\n$style 12\n");
2873                 }
2874                 SendToICS(str);
2875                 NotifyFrontendLogin();
2876                 intfSet = TRUE;
2877             }
2878
2879             if (started == STARTED_COMMENT) {
2880                 /* Accumulate characters in comment */
2881                 parse[parse_pos++] = buf[i];
2882                 if (buf[i] == '\n') {
2883                     parse[parse_pos] = NULLCHAR;
2884                     if(chattingPartner>=0) {
2885                         char mess[MSG_SIZ];
2886                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2887                         OutputChatMessage(chattingPartner, mess);
2888                         chattingPartner = -1;
2889                         next_out = i+1; // [HGM] suppress printing in ICS window
2890                     } else
2891                     if(!suppressKibitz) // [HGM] kibitz
2892                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2893                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2894                         int nrDigit = 0, nrAlph = 0, j;
2895                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2896                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2897                         parse[parse_pos] = NULLCHAR;
2898                         // try to be smart: if it does not look like search info, it should go to
2899                         // ICS interaction window after all, not to engine-output window.
2900                         for(j=0; j<parse_pos; j++) { // count letters and digits
2901                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2902                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2903                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2904                         }
2905                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2906                             int depth=0; float score;
2907                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2908                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2909                                 pvInfoList[forwardMostMove-1].depth = depth;
2910                                 pvInfoList[forwardMostMove-1].score = 100*score;
2911                             }
2912                             OutputKibitz(suppressKibitz, parse);
2913                         } else {
2914                             char tmp[MSG_SIZ];
2915                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2916                             SendToPlayer(tmp, strlen(tmp));
2917                         }
2918                         next_out = i+1; // [HGM] suppress printing in ICS window
2919                     }
2920                     started = STARTED_NONE;
2921                 } else {
2922                     /* Don't match patterns against characters in comment */
2923                     i++;
2924                     continue;
2925                 }
2926             }
2927             if (started == STARTED_CHATTER) {
2928                 if (buf[i] != '\n') {
2929                     /* Don't match patterns against characters in chatter */
2930                     i++;
2931                     continue;
2932                 }
2933                 started = STARTED_NONE;
2934                 if(suppressKibitz) next_out = i+1;
2935             }
2936
2937             /* Kludge to deal with rcmd protocol */
2938             if (firstTime && looking_at(buf, &i, "\001*")) {
2939                 DisplayFatalError(&buf[1], 0, 1);
2940                 continue;
2941             } else {
2942                 firstTime = FALSE;
2943             }
2944
2945             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2946                 ics_type = ICS_ICC;
2947                 ics_prefix = "/";
2948                 if (appData.debugMode)
2949                   fprintf(debugFP, "ics_type %d\n", ics_type);
2950                 continue;
2951             }
2952             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2953                 ics_type = ICS_FICS;
2954                 ics_prefix = "$";
2955                 if (appData.debugMode)
2956                   fprintf(debugFP, "ics_type %d\n", ics_type);
2957                 continue;
2958             }
2959             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2960                 ics_type = ICS_CHESSNET;
2961                 ics_prefix = "/";
2962                 if (appData.debugMode)
2963                   fprintf(debugFP, "ics_type %d\n", ics_type);
2964                 continue;
2965             }
2966
2967             if (!loggedOn &&
2968                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2969                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2970                  looking_at(buf, &i, "will be \"*\""))) {
2971               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2972               continue;
2973             }
2974
2975             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2976               char buf[MSG_SIZ];
2977               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2978               DisplayIcsInteractionTitle(buf);
2979               have_set_title = TRUE;
2980             }
2981
2982             /* skip finger notes */
2983             if (started == STARTED_NONE &&
2984                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2985                  (buf[i] == '1' && buf[i+1] == '0')) &&
2986                 buf[i+2] == ':' && buf[i+3] == ' ') {
2987               started = STARTED_CHATTER;
2988               i += 3;
2989               continue;
2990             }
2991
2992             oldi = i;
2993             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2994             if(appData.seekGraph) {
2995                 if(soughtPending && MatchSoughtLine(buf+i)) {
2996                     i = strstr(buf+i, "rated") - buf;
2997                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2998                     next_out = leftover_start = i;
2999                     started = STARTED_CHATTER;
3000                     suppressKibitz = TRUE;
3001                     continue;
3002                 }
3003                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3004                         && looking_at(buf, &i, "* ads displayed")) {
3005                     soughtPending = FALSE;
3006                     seekGraphUp = TRUE;
3007                     DrawSeekGraph();
3008                     continue;
3009                 }
3010                 if(appData.autoRefresh) {
3011                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3012                         int s = (ics_type == ICS_ICC); // ICC format differs
3013                         if(seekGraphUp)
3014                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3015                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3016                         looking_at(buf, &i, "*% "); // eat prompt
3017                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3018                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3019                         next_out = i; // suppress
3020                         continue;
3021                     }
3022                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3023                         char *p = star_match[0];
3024                         while(*p) {
3025                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3026                             while(*p && *p++ != ' '); // next
3027                         }
3028                         looking_at(buf, &i, "*% "); // eat prompt
3029                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3030                         next_out = i;
3031                         continue;
3032                     }
3033                 }
3034             }
3035
3036             /* skip formula vars */
3037             if (started == STARTED_NONE &&
3038                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3039               started = STARTED_CHATTER;
3040               i += 3;
3041               continue;
3042             }
3043
3044             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3045             if (appData.autoKibitz && started == STARTED_NONE &&
3046                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3047                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3048                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3049                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3050                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3051                         suppressKibitz = TRUE;
3052                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053                         next_out = i;
3054                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3055                                 && (gameMode == IcsPlayingWhite)) ||
3056                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3057                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3058                             started = STARTED_CHATTER; // own kibitz we simply discard
3059                         else {
3060                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3061                             parse_pos = 0; parse[0] = NULLCHAR;
3062                             savingComment = TRUE;
3063                             suppressKibitz = gameMode != IcsObserving ? 2 :
3064                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3065                         }
3066                         continue;
3067                 } else
3068                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3069                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3070                          && atoi(star_match[0])) {
3071                     // suppress the acknowledgements of our own autoKibitz
3072                     char *p;
3073                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3075                     SendToPlayer(star_match[0], strlen(star_match[0]));
3076                     if(looking_at(buf, &i, "*% ")) // eat prompt
3077                         suppressKibitz = FALSE;
3078                     next_out = i;
3079                     continue;
3080                 }
3081             } // [HGM] kibitz: end of patch
3082
3083             // [HGM] chat: intercept tells by users for which we have an open chat window
3084             channel = -1;
3085             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3086                                            looking_at(buf, &i, "* whispers:") ||
3087                                            looking_at(buf, &i, "* kibitzes:") ||
3088                                            looking_at(buf, &i, "* shouts:") ||
3089                                            looking_at(buf, &i, "* c-shouts:") ||
3090                                            looking_at(buf, &i, "--> * ") ||
3091                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3092                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3093                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3094                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3095                 int p;
3096                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3097                 chattingPartner = -1;
3098
3099                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3100                 for(p=0; p<MAX_CHAT; p++) {
3101                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3102                     talker[0] = '['; strcat(talker, "] ");
3103                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3104                     chattingPartner = p; break;
3105                     }
3106                 } else
3107                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3108                 for(p=0; p<MAX_CHAT; p++) {
3109                     if(!strcmp("kibitzes", chatPartner[p])) {
3110                         talker[0] = '['; strcat(talker, "] ");
3111                         chattingPartner = p; break;
3112                     }
3113                 } else
3114                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3115                 for(p=0; p<MAX_CHAT; p++) {
3116                     if(!strcmp("whispers", chatPartner[p])) {
3117                         talker[0] = '['; strcat(talker, "] ");
3118                         chattingPartner = p; break;
3119                     }
3120                 } else
3121                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3122                   if(buf[i-8] == '-' && buf[i-3] == 't')
3123                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3124                     if(!strcmp("c-shouts", chatPartner[p])) {
3125                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3126                         chattingPartner = p; break;
3127                     }
3128                   }
3129                   if(chattingPartner < 0)
3130                   for(p=0; p<MAX_CHAT; p++) {
3131                     if(!strcmp("shouts", chatPartner[p])) {
3132                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3133                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3134                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3135                         chattingPartner = p; break;
3136                     }
3137                   }
3138                 }
3139                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3140                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3141                     talker[0] = 0; Colorize(ColorTell, FALSE);
3142                     chattingPartner = p; break;
3143                 }
3144                 if(chattingPartner<0) i = oldi; else {
3145                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3146                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3147                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3148                     started = STARTED_COMMENT;
3149                     parse_pos = 0; parse[0] = NULLCHAR;
3150                     savingComment = 3 + chattingPartner; // counts as TRUE
3151                     suppressKibitz = TRUE;
3152                     continue;
3153                 }
3154             } // [HGM] chat: end of patch
3155
3156           backup = i;
3157             if (appData.zippyTalk || appData.zippyPlay) {
3158                 /* [DM] Backup address for color zippy lines */
3159 #if ZIPPY
3160                if (loggedOn == TRUE)
3161                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3162                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3163 #endif
3164             } // [DM] 'else { ' deleted
3165                 if (
3166                     /* Regular tells and says */
3167                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3168                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3169                     looking_at(buf, &i, "* says: ") ||
3170                     /* Don't color "message" or "messages" output */
3171                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3172                     looking_at(buf, &i, "*. * at *:*: ") ||
3173                     looking_at(buf, &i, "--* (*:*): ") ||
3174                     /* Message notifications (same color as tells) */
3175                     looking_at(buf, &i, "* has left a message ") ||
3176                     looking_at(buf, &i, "* just sent you a message:\n") ||
3177                     /* Whispers and kibitzes */
3178                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3179                     looking_at(buf, &i, "* kibitzes: ") ||
3180                     /* Channel tells */
3181                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3182
3183                   if (tkind == 1 && strchr(star_match[0], ':')) {
3184                       /* Avoid "tells you:" spoofs in channels */
3185                      tkind = 3;
3186                   }
3187                   if (star_match[0][0] == NULLCHAR ||
3188                       strchr(star_match[0], ' ') ||
3189                       (tkind == 3 && strchr(star_match[1], ' '))) {
3190                     /* Reject bogus matches */
3191                     i = oldi;
3192                   } else {
3193                     if (appData.colorize) {
3194                       if (oldi > next_out) {
3195                         SendToPlayer(&buf[next_out], oldi - next_out);
3196                         next_out = oldi;
3197                       }
3198                       switch (tkind) {
3199                       case 1:
3200                         Colorize(ColorTell, FALSE);
3201                         curColor = ColorTell;
3202                         break;
3203                       case 2:
3204                         Colorize(ColorKibitz, FALSE);
3205                         curColor = ColorKibitz;
3206                         break;
3207                       case 3:
3208                         p = strrchr(star_match[1], '(');
3209                         if (p == NULL) {
3210                           p = star_match[1];
3211                         } else {
3212                           p++;
3213                         }
3214                         if (atoi(p) == 1) {
3215                           Colorize(ColorChannel1, FALSE);
3216                           curColor = ColorChannel1;
3217                         } else {
3218                           Colorize(ColorChannel, FALSE);
3219                           curColor = ColorChannel;
3220                         }
3221                         break;
3222                       case 5:
3223                         curColor = ColorNormal;
3224                         break;
3225                       }
3226                     }
3227                     if (started == STARTED_NONE && appData.autoComment &&
3228                         (gameMode == IcsObserving ||
3229                          gameMode == IcsPlayingWhite ||
3230                          gameMode == IcsPlayingBlack)) {
3231                       parse_pos = i - oldi;
3232                       memcpy(parse, &buf[oldi], parse_pos);
3233                       parse[parse_pos] = NULLCHAR;
3234                       started = STARTED_COMMENT;
3235                       savingComment = TRUE;
3236                     } else {
3237                       started = STARTED_CHATTER;
3238                       savingComment = FALSE;
3239                     }
3240                     loggedOn = TRUE;
3241                     continue;
3242                   }
3243                 }
3244
3245                 if (looking_at(buf, &i, "* s-shouts: ") ||
3246                     looking_at(buf, &i, "* c-shouts: ")) {
3247                     if (appData.colorize) {
3248                         if (oldi > next_out) {
3249                             SendToPlayer(&buf[next_out], oldi - next_out);
3250                             next_out = oldi;
3251                         }
3252                         Colorize(ColorSShout, FALSE);
3253                         curColor = ColorSShout;
3254                     }
3255                     loggedOn = TRUE;
3256                     started = STARTED_CHATTER;
3257                     continue;
3258                 }
3259
3260                 if (looking_at(buf, &i, "--->")) {
3261                     loggedOn = TRUE;
3262                     continue;
3263                 }
3264
3265                 if (looking_at(buf, &i, "* shouts: ") ||
3266                     looking_at(buf, &i, "--> ")) {
3267                     if (appData.colorize) {
3268                         if (oldi > next_out) {
3269                             SendToPlayer(&buf[next_out], oldi - next_out);
3270                             next_out = oldi;
3271                         }
3272                         Colorize(ColorShout, FALSE);
3273                         curColor = ColorShout;
3274                     }
3275                     loggedOn = TRUE;
3276                     started = STARTED_CHATTER;
3277                     continue;
3278                 }
3279
3280                 if (looking_at( buf, &i, "Challenge:")) {
3281                     if (appData.colorize) {
3282                         if (oldi > next_out) {
3283                             SendToPlayer(&buf[next_out], oldi - next_out);
3284                             next_out = oldi;
3285                         }
3286                         Colorize(ColorChallenge, FALSE);
3287                         curColor = ColorChallenge;
3288                     }
3289                     loggedOn = TRUE;
3290                     continue;
3291                 }
3292
3293                 if (looking_at(buf, &i, "* offers you") ||
3294                     looking_at(buf, &i, "* offers to be") ||
3295                     looking_at(buf, &i, "* would like to") ||
3296                     looking_at(buf, &i, "* requests to") ||
3297                     looking_at(buf, &i, "Your opponent offers") ||
3298                     looking_at(buf, &i, "Your opponent requests")) {
3299
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorRequest, FALSE);
3306                         curColor = ColorRequest;
3307                     }
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "* (*) seeking")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorSeek, FALSE);
3318                         curColor = ColorSeek;
3319                     }
3320                     continue;
3321             }
3322
3323           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3324
3325             if (looking_at(buf, &i, "\\   ")) {
3326                 if (prevColor != ColorNormal) {
3327                     if (oldi > next_out) {
3328                         SendToPlayer(&buf[next_out], oldi - next_out);
3329                         next_out = oldi;
3330                     }
3331                     Colorize(prevColor, TRUE);
3332                     curColor = prevColor;
3333                 }
3334                 if (savingComment) {
3335                     parse_pos = i - oldi;
3336                     memcpy(parse, &buf[oldi], parse_pos);
3337                     parse[parse_pos] = NULLCHAR;
3338                     started = STARTED_COMMENT;
3339                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3340                         chattingPartner = savingComment - 3; // kludge to remember the box
3341                 } else {
3342                     started = STARTED_CHATTER;
3343                 }
3344                 continue;
3345             }
3346
3347             if (looking_at(buf, &i, "Black Strength :") ||
3348                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3349                 looking_at(buf, &i, "<10>") ||
3350                 looking_at(buf, &i, "#@#")) {
3351                 /* Wrong board style */
3352                 loggedOn = TRUE;
3353                 SendToICS(ics_prefix);
3354                 SendToICS("set style 12\n");
3355                 SendToICS(ics_prefix);
3356                 SendToICS("refresh\n");
3357                 continue;
3358             }
3359
3360             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3361                 ICSInitScript();
3362                 have_sent_ICS_logon = 1;
3363                 continue;
3364             }
3365
3366             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3367                 (looking_at(buf, &i, "\n<12> ") ||
3368                  looking_at(buf, &i, "<12> "))) {
3369                 loggedOn = TRUE;
3370                 if (oldi > next_out) {
3371                     SendToPlayer(&buf[next_out], oldi - next_out);
3372                 }
3373                 next_out = i;
3374                 started = STARTED_BOARD;
3375                 parse_pos = 0;
3376                 continue;
3377             }
3378
3379             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3380                 looking_at(buf, &i, "<b1> ")) {
3381                 if (oldi > next_out) {
3382                     SendToPlayer(&buf[next_out], oldi - next_out);
3383                 }
3384                 next_out = i;
3385                 started = STARTED_HOLDINGS;
3386                 parse_pos = 0;
3387                 continue;
3388             }
3389
3390             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3391                 loggedOn = TRUE;
3392                 /* Header for a move list -- first line */
3393
3394                 switch (ics_getting_history) {
3395                   case H_FALSE:
3396                     switch (gameMode) {
3397                       case IcsIdle:
3398                       case BeginningOfGame:
3399                         /* User typed "moves" or "oldmoves" while we
3400                            were idle.  Pretend we asked for these
3401                            moves and soak them up so user can step
3402                            through them and/or save them.
3403                            */
3404                         Reset(FALSE, TRUE);
3405                         gameMode = IcsObserving;
3406                         ModeHighlight();
3407                         ics_gamenum = -1;
3408                         ics_getting_history = H_GOT_UNREQ_HEADER;
3409                         break;
3410                       case EditGame: /*?*/
3411                       case EditPosition: /*?*/
3412                         /* Should above feature work in these modes too? */
3413                         /* For now it doesn't */
3414                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3415                         break;
3416                       default:
3417                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3418                         break;
3419                     }
3420                     break;
3421                   case H_REQUESTED:
3422                     /* Is this the right one? */
3423                     if (gameInfo.white && gameInfo.black &&
3424                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3425                         strcmp(gameInfo.black, star_match[2]) == 0) {
3426                         /* All is well */
3427                         ics_getting_history = H_GOT_REQ_HEADER;
3428                     }
3429                     break;
3430                   case H_GOT_REQ_HEADER:
3431                   case H_GOT_UNREQ_HEADER:
3432                   case H_GOT_UNWANTED_HEADER:
3433                   case H_GETTING_MOVES:
3434                     /* Should not happen */
3435                     DisplayError(_("Error gathering move list: two headers"), 0);
3436                     ics_getting_history = H_FALSE;
3437                     break;
3438                 }
3439
3440                 /* Save player ratings into gameInfo if needed */
3441                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3442                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3443                     (gameInfo.whiteRating == -1 ||
3444                      gameInfo.blackRating == -1)) {
3445
3446                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3447                     gameInfo.blackRating = string_to_rating(star_match[3]);
3448                     if (appData.debugMode)
3449                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3450                               gameInfo.whiteRating, gameInfo.blackRating);
3451                 }
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i,
3456               "* * match, initial time: * minute*, increment: * second")) {
3457                 /* Header for a move list -- second line */
3458                 /* Initial board will follow if this is a wild game */
3459                 if (gameInfo.event != NULL) free(gameInfo.event);
3460                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3461                 gameInfo.event = StrSave(str);
3462                 /* [HGM] we switched variant. Translate boards if needed. */
3463                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3464                 continue;
3465             }
3466
3467             if (looking_at(buf, &i, "Move  ")) {
3468                 /* Beginning of a move list */
3469                 switch (ics_getting_history) {
3470                   case H_FALSE:
3471                     /* Normally should not happen */
3472                     /* Maybe user hit reset while we were parsing */
3473                     break;
3474                   case H_REQUESTED:
3475                     /* Happens if we are ignoring a move list that is not
3476                      * the one we just requested.  Common if the user
3477                      * tries to observe two games without turning off
3478                      * getMoveList */
3479                     break;
3480                   case H_GETTING_MOVES:
3481                     /* Should not happen */
3482                     DisplayError(_("Error gathering move list: nested"), 0);
3483                     ics_getting_history = H_FALSE;
3484                     break;
3485                   case H_GOT_REQ_HEADER:
3486                     ics_getting_history = H_GETTING_MOVES;
3487                     started = STARTED_MOVES;
3488                     parse_pos = 0;
3489                     if (oldi > next_out) {
3490                         SendToPlayer(&buf[next_out], oldi - next_out);
3491                     }
3492                     break;
3493                   case H_GOT_UNREQ_HEADER:
3494                     ics_getting_history = H_GETTING_MOVES;
3495                     started = STARTED_MOVES_NOHIDE;
3496                     parse_pos = 0;
3497                     break;
3498                   case H_GOT_UNWANTED_HEADER:
3499                     ics_getting_history = H_FALSE;
3500                     break;
3501                 }
3502                 continue;
3503             }
3504
3505             if (looking_at(buf, &i, "% ") ||
3506                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3507                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3508                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3509                     soughtPending = FALSE;
3510                     seekGraphUp = TRUE;
3511                     DrawSeekGraph();
3512                 }
3513                 if(suppressKibitz) next_out = i;
3514                 savingComment = FALSE;
3515                 suppressKibitz = 0;
3516                 switch (started) {
3517                   case STARTED_MOVES:
3518                   case STARTED_MOVES_NOHIDE:
3519                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3520                     parse[parse_pos + i - oldi] = NULLCHAR;
3521                     ParseGameHistory(parse);
3522 #if ZIPPY
3523                     if (appData.zippyPlay && first.initDone) {
3524                         FeedMovesToProgram(&first, forwardMostMove);
3525                         if (gameMode == IcsPlayingWhite) {
3526                             if (WhiteOnMove(forwardMostMove)) {
3527                                 if (first.sendTime) {
3528                                   if (first.useColors) {
3529                                     SendToProgram("black\n", &first);
3530                                   }
3531                                   SendTimeRemaining(&first, TRUE);
3532                                 }
3533                                 if (first.useColors) {
3534                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3535                                 }
3536                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3537                                 first.maybeThinking = TRUE;
3538                             } else {
3539                                 if (first.usePlayother) {
3540                                   if (first.sendTime) {
3541                                     SendTimeRemaining(&first, TRUE);
3542                                   }
3543                                   SendToProgram("playother\n", &first);
3544                                   firstMove = FALSE;
3545                                 } else {
3546                                   firstMove = TRUE;
3547                                 }
3548                             }
3549                         } else if (gameMode == IcsPlayingBlack) {
3550                             if (!WhiteOnMove(forwardMostMove)) {
3551                                 if (first.sendTime) {
3552                                   if (first.useColors) {
3553                                     SendToProgram("white\n", &first);
3554                                   }
3555                                   SendTimeRemaining(&first, FALSE);
3556                                 }
3557                                 if (first.useColors) {
3558                                   SendToProgram("black\n", &first);
3559                                 }
3560                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3561                                 first.maybeThinking = TRUE;
3562                             } else {
3563                                 if (first.usePlayother) {
3564                                   if (first.sendTime) {
3565                                     SendTimeRemaining(&first, FALSE);
3566                                   }
3567                                   SendToProgram("playother\n", &first);
3568                                   firstMove = FALSE;
3569                                 } else {
3570                                   firstMove = TRUE;
3571                                 }
3572                             }
3573                         }
3574                     }
3575 #endif
3576                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3577                         /* Moves came from oldmoves or moves command
3578                            while we weren't doing anything else.
3579                            */
3580                         currentMove = forwardMostMove;
3581                         ClearHighlights();/*!!could figure this out*/
3582                         flipView = appData.flipView;
3583                         DrawPosition(TRUE, boards[currentMove]);
3584                         DisplayBothClocks();
3585                         snprintf(str, MSG_SIZ, "%s vs. %s",
3586                                 gameInfo.white, gameInfo.black);
3587                         DisplayTitle(str);
3588                         gameMode = IcsIdle;
3589                     } else {
3590                         /* Moves were history of an active game */
3591                         if (gameInfo.resultDetails != NULL) {
3592                             free(gameInfo.resultDetails);
3593                             gameInfo.resultDetails = NULL;
3594                         }
3595                     }
3596                     HistorySet(parseList, backwardMostMove,
3597                                forwardMostMove, currentMove-1);
3598                     DisplayMove(currentMove - 1);
3599                     if (started == STARTED_MOVES) next_out = i;
3600                     started = STARTED_NONE;
3601                     ics_getting_history = H_FALSE;
3602                     break;
3603
3604                   case STARTED_OBSERVE:
3605                     started = STARTED_NONE;
3606                     SendToICS(ics_prefix);
3607                     SendToICS("refresh\n");
3608                     break;
3609
3610                   default:
3611                     break;
3612                 }
3613                 if(bookHit) { // [HGM] book: simulate book reply
3614                     static char bookMove[MSG_SIZ]; // a bit generous?
3615
3616                     programStats.nodes = programStats.depth = programStats.time =
3617                     programStats.score = programStats.got_only_move = 0;
3618                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3619
3620                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3621                     strcat(bookMove, bookHit);
3622                     HandleMachineMove(bookMove, &first);
3623                 }
3624                 continue;
3625             }
3626
3627             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3628                  started == STARTED_HOLDINGS ||
3629                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3630                 /* Accumulate characters in move list or board */
3631                 parse[parse_pos++] = buf[i];
3632             }
3633
3634             /* Start of game messages.  Mostly we detect start of game
3635                when the first board image arrives.  On some versions
3636                of the ICS, though, we need to do a "refresh" after starting
3637                to observe in order to get the current board right away. */
3638             if (looking_at(buf, &i, "Adding game * to observation list")) {
3639                 started = STARTED_OBSERVE;
3640                 continue;
3641             }
3642
3643             /* Handle auto-observe */
3644             if (appData.autoObserve &&
3645                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3646                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3647                 char *player;
3648                 /* Choose the player that was highlighted, if any. */
3649                 if (star_match[0][0] == '\033' ||
3650                     star_match[1][0] != '\033') {
3651                     player = star_match[0];
3652                 } else {
3653                     player = star_match[2];
3654                 }
3655                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3656                         ics_prefix, StripHighlightAndTitle(player));
3657                 SendToICS(str);
3658
3659                 /* Save ratings from notify string */
3660                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3661                 player1Rating = string_to_rating(star_match[1]);
3662                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3663                 player2Rating = string_to_rating(star_match[3]);
3664
3665                 if (appData.debugMode)
3666                   fprintf(debugFP,
3667                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3668                           player1Name, player1Rating,
3669                           player2Name, player2Rating);
3670
3671                 continue;
3672             }
3673
3674             /* Deal with automatic examine mode after a game,
3675                and with IcsObserving -> IcsExamining transition */
3676             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3677                 looking_at(buf, &i, "has made you an examiner of game *")) {
3678
3679                 int gamenum = atoi(star_match[0]);
3680                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3681                     gamenum == ics_gamenum) {
3682                     /* We were already playing or observing this game;
3683                        no need to refetch history */
3684                     gameMode = IcsExamining;
3685                     if (pausing) {
3686                         pauseExamForwardMostMove = forwardMostMove;
3687                     } else if (currentMove < forwardMostMove) {
3688                         ForwardInner(forwardMostMove);
3689                     }
3690                 } else {
3691                     /* I don't think this case really can happen */
3692                     SendToICS(ics_prefix);
3693                     SendToICS("refresh\n");
3694                 }
3695                 continue;
3696             }
3697
3698             /* Error messages */
3699 //          if (ics_user_moved) {
3700             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3701                 if (looking_at(buf, &i, "Illegal move") ||
3702                     looking_at(buf, &i, "Not a legal move") ||
3703                     looking_at(buf, &i, "Your king is in check") ||
3704                     looking_at(buf, &i, "It isn't your turn") ||
3705                     looking_at(buf, &i, "It is not your move")) {
3706                     /* Illegal move */
3707                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3708                         currentMove = forwardMostMove-1;
3709                         DisplayMove(currentMove - 1); /* before DMError */
3710                         DrawPosition(FALSE, boards[currentMove]);
3711                         SwitchClocks(forwardMostMove-1); // [HGM] race
3712                         DisplayBothClocks();
3713                     }
3714                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3715                     ics_user_moved = 0;
3716                     continue;
3717                 }
3718             }
3719
3720             if (looking_at(buf, &i, "still have time") ||
3721                 looking_at(buf, &i, "not out of time") ||
3722                 looking_at(buf, &i, "either player is out of time") ||
3723                 looking_at(buf, &i, "has timeseal; checking")) {
3724                 /* We must have called his flag a little too soon */
3725                 whiteFlag = blackFlag = FALSE;
3726                 continue;
3727             }
3728
3729             if (looking_at(buf, &i, "added * seconds to") ||
3730                 looking_at(buf, &i, "seconds were added to")) {
3731                 /* Update the clocks */
3732                 SendToICS(ics_prefix);
3733                 SendToICS("refresh\n");
3734                 continue;
3735             }
3736
3737             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3738                 ics_clock_paused = TRUE;
3739                 StopClocks();
3740                 continue;
3741             }
3742
3743             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3744                 ics_clock_paused = FALSE;
3745                 StartClocks();
3746                 continue;
3747             }
3748
3749             /* Grab player ratings from the Creating: message.
3750                Note we have to check for the special case when
3751                the ICS inserts things like [white] or [black]. */
3752             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3753                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3754                 /* star_matches:
3755                    0    player 1 name (not necessarily white)
3756                    1    player 1 rating
3757                    2    empty, white, or black (IGNORED)
3758                    3    player 2 name (not necessarily black)
3759                    4    player 2 rating
3760
3761                    The names/ratings are sorted out when the game
3762                    actually starts (below).
3763                 */
3764                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3765                 player1Rating = string_to_rating(star_match[1]);
3766                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3767                 player2Rating = string_to_rating(star_match[4]);
3768
3769                 if (appData.debugMode)
3770                   fprintf(debugFP,
3771                           "Ratings from 'Creating:' %s %d, %s %d\n",
3772                           player1Name, player1Rating,
3773                           player2Name, player2Rating);
3774
3775                 continue;
3776             }
3777
3778             /* Improved generic start/end-of-game messages */
3779             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3780                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3781                 /* If tkind == 0: */
3782                 /* star_match[0] is the game number */
3783                 /*           [1] is the white player's name */
3784                 /*           [2] is the black player's name */
3785                 /* For end-of-game: */
3786                 /*           [3] is the reason for the game end */
3787                 /*           [4] is a PGN end game-token, preceded by " " */
3788                 /* For start-of-game: */
3789                 /*           [3] begins with "Creating" or "Continuing" */
3790                 /*           [4] is " *" or empty (don't care). */
3791                 int gamenum = atoi(star_match[0]);
3792                 char *whitename, *blackname, *why, *endtoken;
3793                 ChessMove endtype = EndOfFile;
3794
3795                 if (tkind == 0) {
3796                   whitename = star_match[1];
3797                   blackname = star_match[2];
3798                   why = star_match[3];
3799                   endtoken = star_match[4];
3800                 } else {
3801                   whitename = star_match[1];
3802                   blackname = star_match[3];
3803                   why = star_match[5];
3804                   endtoken = star_match[6];
3805                 }
3806
3807                 /* Game start messages */
3808                 if (strncmp(why, "Creating ", 9) == 0 ||
3809                     strncmp(why, "Continuing ", 11) == 0) {
3810                     gs_gamenum = gamenum;
3811                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3812                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3813 #if ZIPPY
3814                     if (appData.zippyPlay) {
3815                         ZippyGameStart(whitename, blackname);
3816                     }
3817 #endif /*ZIPPY*/
3818                     partnerBoardValid = FALSE; // [HGM] bughouse
3819                     continue;
3820                 }
3821
3822                 /* Game end messages */
3823                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3824                     ics_gamenum != gamenum) {
3825                     continue;
3826                 }
3827                 while (endtoken[0] == ' ') endtoken++;
3828                 switch (endtoken[0]) {
3829                   case '*':
3830                   default:
3831                     endtype = GameUnfinished;
3832                     break;
3833                   case '0':
3834                     endtype = BlackWins;
3835                     break;
3836                   case '1':
3837                     if (endtoken[1] == '/')
3838                       endtype = GameIsDrawn;
3839                     else
3840                       endtype = WhiteWins;
3841                     break;
3842                 }
3843                 GameEnds(endtype, why, GE_ICS);
3844 #if ZIPPY
3845                 if (appData.zippyPlay && first.initDone) {
3846                     ZippyGameEnd(endtype, why);
3847                     if (first.pr == NULL) {
3848                       /* Start the next process early so that we'll
3849                          be ready for the next challenge */
3850                       StartChessProgram(&first);
3851                     }
3852                     /* Send "new" early, in case this command takes
3853                        a long time to finish, so that we'll be ready
3854                        for the next challenge. */
3855                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3856                     Reset(TRUE, TRUE);
3857                 }
3858 #endif /*ZIPPY*/
3859                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3860                 continue;
3861             }
3862
3863             if (looking_at(buf, &i, "Removing game * from observation") ||
3864                 looking_at(buf, &i, "no longer observing game *") ||
3865                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3866                 if (gameMode == IcsObserving &&
3867                     atoi(star_match[0]) == ics_gamenum)
3868                   {
3869                       /* icsEngineAnalyze */
3870                       if (appData.icsEngineAnalyze) {
3871                             ExitAnalyzeMode();
3872                             ModeHighlight();
3873                       }
3874                       StopClocks();
3875                       gameMode = IcsIdle;
3876                       ics_gamenum = -1;
3877                       ics_user_moved = FALSE;
3878                   }
3879                 continue;
3880             }
3881
3882             if (looking_at(buf, &i, "no longer examining game *")) {
3883                 if (gameMode == IcsExamining &&
3884                     atoi(star_match[0]) == ics_gamenum)
3885                   {
3886                       gameMode = IcsIdle;
3887                       ics_gamenum = -1;
3888                       ics_user_moved = FALSE;
3889                   }
3890                 continue;
3891             }
3892
3893             /* Advance leftover_start past any newlines we find,
3894                so only partial lines can get reparsed */
3895             if (looking_at(buf, &i, "\n")) {
3896                 prevColor = curColor;
3897                 if (curColor != ColorNormal) {
3898                     if (oldi > next_out) {
3899                         SendToPlayer(&buf[next_out], oldi - next_out);
3900                         next_out = oldi;
3901                     }
3902                     Colorize(ColorNormal, FALSE);
3903                     curColor = ColorNormal;
3904                 }
3905                 if (started == STARTED_BOARD) {
3906                     started = STARTED_NONE;
3907                     parse[parse_pos] = NULLCHAR;
3908                     ParseBoard12(parse);
3909                     ics_user_moved = 0;
3910
3911                     /* Send premove here */
3912                     if (appData.premove) {
3913                       char str[MSG_SIZ];
3914                       if (currentMove == 0 &&
3915                           gameMode == IcsPlayingWhite &&
3916                           appData.premoveWhite) {
3917                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3918                         if (appData.debugMode)
3919                           fprintf(debugFP, "Sending premove:\n");
3920                         SendToICS(str);
3921                       } else if (currentMove == 1 &&
3922                                  gameMode == IcsPlayingBlack &&
3923                                  appData.premoveBlack) {
3924                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3925                         if (appData.debugMode)
3926                           fprintf(debugFP, "Sending premove:\n");
3927                         SendToICS(str);
3928                       } else if (gotPremove) {
3929                         gotPremove = 0;
3930                         ClearPremoveHighlights();
3931                         if (appData.debugMode)
3932                           fprintf(debugFP, "Sending premove:\n");
3933                           UserMoveEvent(premoveFromX, premoveFromY,
3934                                         premoveToX, premoveToY,
3935                                         premovePromoChar);
3936                       }
3937                     }
3938
3939                     /* Usually suppress following prompt */
3940                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3941                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3942                         if (looking_at(buf, &i, "*% ")) {
3943                             savingComment = FALSE;
3944                             suppressKibitz = 0;
3945                         }
3946                     }
3947                     next_out = i;
3948                 } else if (started == STARTED_HOLDINGS) {
3949                     int gamenum;
3950                     char new_piece[MSG_SIZ];
3951                     started = STARTED_NONE;
3952                     parse[parse_pos] = NULLCHAR;
3953                     if (appData.debugMode)
3954                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3955                                                         parse, currentMove);
3956                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3957                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3958                         if (gameInfo.variant == VariantNormal) {
3959                           /* [HGM] We seem to switch variant during a game!
3960                            * Presumably no holdings were displayed, so we have
3961                            * to move the position two files to the right to
3962                            * create room for them!
3963                            */
3964                           VariantClass newVariant;
3965                           switch(gameInfo.boardWidth) { // base guess on board width
3966                                 case 9:  newVariant = VariantShogi; break;
3967                                 case 10: newVariant = VariantGreat; break;
3968                                 default: newVariant = VariantCrazyhouse; break;
3969                           }
3970                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3971                           /* Get a move list just to see the header, which
3972                              will tell us whether this is really bug or zh */
3973                           if (ics_getting_history == H_FALSE) {
3974                             ics_getting_history = H_REQUESTED;
3975                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3976                             SendToICS(str);
3977                           }
3978                         }
3979                         new_piece[0] = NULLCHAR;
3980                         sscanf(parse, "game %d white [%s black [%s <- %s",
3981                                &gamenum, white_holding, black_holding,
3982                                new_piece);
3983                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3984                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3985                         /* [HGM] copy holdings to board holdings area */
3986                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3987                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3988                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3989 #if ZIPPY
3990                         if (appData.zippyPlay && first.initDone) {
3991                             ZippyHoldings(white_holding, black_holding,
3992                                           new_piece);
3993                         }
3994 #endif /*ZIPPY*/
3995                         if (tinyLayout || smallLayout) {
3996                             char wh[16], bh[16];
3997                             PackHolding(wh, white_holding);
3998                             PackHolding(bh, black_holding);
3999                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4000                                     gameInfo.white, gameInfo.black);
4001                         } else {
4002                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4003                                     gameInfo.white, white_holding,
4004                                     gameInfo.black, black_holding);
4005                         }
4006                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4007                         DrawPosition(FALSE, boards[currentMove]);
4008                         DisplayTitle(str);
4009                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4010                         sscanf(parse, "game %d white [%s black [%s <- %s",
4011                                &gamenum, white_holding, black_holding,
4012                                new_piece);
4013                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4014                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4015                         /* [HGM] copy holdings to partner-board holdings area */
4016                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4017                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4018                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4019                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4020                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4021                       }
4022                     }
4023                     /* Suppress following prompt */
4024                     if (looking_at(buf, &i, "*% ")) {
4025                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4026                         savingComment = FALSE;
4027                         suppressKibitz = 0;
4028                     }
4029                     next_out = i;
4030                 }
4031                 continue;
4032             }
4033
4034             i++;                /* skip unparsed character and loop back */
4035         }
4036
4037         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4038 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4039 //          SendToPlayer(&buf[next_out], i - next_out);
4040             started != STARTED_HOLDINGS && leftover_start > next_out) {
4041             SendToPlayer(&buf[next_out], leftover_start - next_out);
4042             next_out = i;
4043         }
4044
4045         leftover_len = buf_len - leftover_start;
4046         /* if buffer ends with something we couldn't parse,
4047            reparse it after appending the next read */
4048
4049     } else if (count == 0) {
4050         RemoveInputSource(isr);
4051         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4052     } else {
4053         DisplayFatalError(_("Error reading from ICS"), error, 1);
4054     }
4055 }
4056
4057
4058 /* Board style 12 looks like this:
4059
4060    <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
4061
4062  * The "<12> " is stripped before it gets to this routine.  The two
4063  * trailing 0's (flip state and clock ticking) are later addition, and
4064  * some chess servers may not have them, or may have only the first.
4065  * Additional trailing fields may be added in the future.
4066  */
4067
4068 #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"
4069
4070 #define RELATION_OBSERVING_PLAYED    0
4071 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4072 #define RELATION_PLAYING_MYMOVE      1
4073 #define RELATION_PLAYING_NOTMYMOVE  -1
4074 #define RELATION_EXAMINING           2
4075 #define RELATION_ISOLATED_BOARD     -3
4076 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4077
4078 void
4079 ParseBoard12(string)
4080      char *string;
4081 {
4082     GameMode newGameMode;
4083     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4084     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4085     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4086     char to_play, board_chars[200];
4087     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4088     char black[32], white[32];
4089     Board board;
4090     int prevMove = currentMove;
4091     int ticking = 2;
4092     ChessMove moveType;
4093     int fromX, fromY, toX, toY;
4094     char promoChar;
4095     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4096     char *bookHit = NULL; // [HGM] book
4097     Boolean weird = FALSE, reqFlag = FALSE;
4098
4099     fromX = fromY = toX = toY = -1;
4100
4101     newGame = FALSE;
4102
4103     if (appData.debugMode)
4104       fprintf(debugFP, _("Parsing board: %s\n"), string);
4105
4106     move_str[0] = NULLCHAR;
4107     elapsed_time[0] = NULLCHAR;
4108     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4109         int  i = 0, j;
4110         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4111             if(string[i] == ' ') { ranks++; files = 0; }
4112             else files++;
4113             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4114             i++;
4115         }
4116         for(j = 0; j <i; j++) board_chars[j] = string[j];
4117         board_chars[i] = '\0';
4118         string += i + 1;
4119     }
4120     n = sscanf(string, PATTERN, &to_play, &double_push,
4121                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4122                &gamenum, white, black, &relation, &basetime, &increment,
4123                &white_stren, &black_stren, &white_time, &black_time,
4124                &moveNum, str, elapsed_time, move_str, &ics_flip,
4125                &ticking);
4126
4127     if (n < 21) {
4128         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4129         DisplayError(str, 0);
4130         return;
4131     }
4132
4133     /* Convert the move number to internal form */
4134     moveNum = (moveNum - 1) * 2;
4135     if (to_play == 'B') moveNum++;
4136     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4137       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4138                         0, 1);
4139       return;
4140     }
4141
4142     switch (relation) {
4143       case RELATION_OBSERVING_PLAYED:
4144       case RELATION_OBSERVING_STATIC:
4145         if (gamenum == -1) {
4146             /* Old ICC buglet */
4147             relation = RELATION_OBSERVING_STATIC;
4148         }
4149         newGameMode = IcsObserving;
4150         break;
4151       case RELATION_PLAYING_MYMOVE:
4152       case RELATION_PLAYING_NOTMYMOVE:
4153         newGameMode =
4154           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4155             IcsPlayingWhite : IcsPlayingBlack;
4156         break;
4157       case RELATION_EXAMINING:
4158         newGameMode = IcsExamining;
4159         break;
4160       case RELATION_ISOLATED_BOARD:
4161       default:
4162         /* Just display this board.  If user was doing something else,
4163            we will forget about it until the next board comes. */
4164         newGameMode = IcsIdle;
4165         break;
4166       case RELATION_STARTING_POSITION:
4167         newGameMode = gameMode;
4168         break;
4169     }
4170
4171     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4172          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4173       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4174       char *toSqr;
4175       for (k = 0; k < ranks; k++) {
4176         for (j = 0; j < files; j++)
4177           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4178         if(gameInfo.holdingsWidth > 1) {
4179              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4180              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4181         }
4182       }
4183       CopyBoard(partnerBoard, board);
4184       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4185         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4186         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4187       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4188       if(toSqr = strchr(str, '-')) {
4189         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4190         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4191       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4192       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4193       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4194       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4195       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4196       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4197                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4198       DisplayMessage(partnerStatus, "");
4199         partnerBoardValid = TRUE;
4200       return;
4201     }
4202
4203     /* Modify behavior for initial board display on move listing
4204        of wild games.
4205        */
4206     switch (ics_getting_history) {
4207       case H_FALSE:
4208       case H_REQUESTED:
4209         break;
4210       case H_GOT_REQ_HEADER:
4211       case H_GOT_UNREQ_HEADER:
4212         /* This is the initial position of the current game */
4213         gamenum = ics_gamenum;
4214         moveNum = 0;            /* old ICS bug workaround */
4215         if (to_play == 'B') {
4216           startedFromSetupPosition = TRUE;
4217           blackPlaysFirst = TRUE;
4218           moveNum = 1;
4219           if (forwardMostMove == 0) forwardMostMove = 1;
4220           if (backwardMostMove == 0) backwardMostMove = 1;
4221           if (currentMove == 0) currentMove = 1;
4222         }
4223         newGameMode = gameMode;
4224         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4225         break;
4226       case H_GOT_UNWANTED_HEADER:
4227         /* This is an initial board that we don't want */
4228         return;
4229       case H_GETTING_MOVES:
4230         /* Should not happen */
4231         DisplayError(_("Error gathering move list: extra board"), 0);
4232         ics_getting_history = H_FALSE;
4233         return;
4234     }
4235
4236    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4237                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4238      /* [HGM] We seem to have switched variant unexpectedly
4239       * Try to guess new variant from board size
4240       */
4241           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4242           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4243           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4244           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4245           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4246           if(!weird) newVariant = VariantNormal;
4247           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4248           /* Get a move list just to see the header, which
4249              will tell us whether this is really bug or zh */
4250           if (ics_getting_history == H_FALSE) {
4251             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4252             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4253             SendToICS(str);
4254           }
4255     }
4256
4257     /* Take action if this is the first board of a new game, or of a
4258        different game than is currently being displayed.  */
4259     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4260         relation == RELATION_ISOLATED_BOARD) {
4261
4262         /* Forget the old game and get the history (if any) of the new one */
4263         if (gameMode != BeginningOfGame) {
4264           Reset(TRUE, TRUE);
4265         }
4266         newGame = TRUE;
4267         if (appData.autoRaiseBoard) BoardToTop();
4268         prevMove = -3;
4269         if (gamenum == -1) {
4270             newGameMode = IcsIdle;
4271         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4272                    appData.getMoveList && !reqFlag) {
4273             /* Need to get game history */
4274             ics_getting_history = H_REQUESTED;
4275             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4276             SendToICS(str);
4277         }
4278
4279         /* Initially flip the board to have black on the bottom if playing
4280            black or if the ICS flip flag is set, but let the user change
4281            it with the Flip View button. */
4282         flipView = appData.autoFlipView ?
4283           (newGameMode == IcsPlayingBlack) || ics_flip :
4284           appData.flipView;
4285
4286         /* Done with values from previous mode; copy in new ones */
4287         gameMode = newGameMode;
4288         ModeHighlight();
4289         ics_gamenum = gamenum;
4290         if (gamenum == gs_gamenum) {
4291             int klen = strlen(gs_kind);
4292             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4293             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4294             gameInfo.event = StrSave(str);
4295         } else {
4296             gameInfo.event = StrSave("ICS game");
4297         }
4298         gameInfo.site = StrSave(appData.icsHost);
4299         gameInfo.date = PGNDate();
4300         gameInfo.round = StrSave("-");
4301         gameInfo.white = StrSave(white);
4302         gameInfo.black = StrSave(black);
4303         timeControl = basetime * 60 * 1000;
4304         timeControl_2 = 0;
4305         timeIncrement = increment * 1000;
4306         movesPerSession = 0;
4307         gameInfo.timeControl = TimeControlTagValue();
4308         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4309   if (appData.debugMode) {
4310     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4311     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4312     setbuf(debugFP, NULL);
4313   }
4314
4315         gameInfo.outOfBook = NULL;
4316
4317         /* Do we have the ratings? */
4318         if (strcmp(player1Name, white) == 0 &&
4319             strcmp(player2Name, black) == 0) {
4320             if (appData.debugMode)
4321               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4322                       player1Rating, player2Rating);
4323             gameInfo.whiteRating = player1Rating;
4324             gameInfo.blackRating = player2Rating;
4325         } else if (strcmp(player2Name, white) == 0 &&
4326                    strcmp(player1Name, black) == 0) {
4327             if (appData.debugMode)
4328               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4329                       player2Rating, player1Rating);
4330             gameInfo.whiteRating = player2Rating;
4331             gameInfo.blackRating = player1Rating;
4332         }
4333         player1Name[0] = player2Name[0] = NULLCHAR;
4334
4335         /* Silence shouts if requested */
4336         if (appData.quietPlay &&
4337             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4338             SendToICS(ics_prefix);
4339             SendToICS("set shout 0\n");
4340         }
4341     }
4342
4343     /* Deal with midgame name changes */
4344     if (!newGame) {
4345         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4346             if (gameInfo.white) free(gameInfo.white);
4347             gameInfo.white = StrSave(white);
4348         }
4349         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4350             if (gameInfo.black) free(gameInfo.black);
4351             gameInfo.black = StrSave(black);
4352         }
4353     }
4354
4355     /* Throw away game result if anything actually changes in examine mode */
4356     if (gameMode == IcsExamining && !newGame) {
4357         gameInfo.result = GameUnfinished;
4358         if (gameInfo.resultDetails != NULL) {
4359             free(gameInfo.resultDetails);
4360             gameInfo.resultDetails = NULL;
4361         }
4362     }
4363
4364     /* In pausing && IcsExamining mode, we ignore boards coming
4365        in if they are in a different variation than we are. */
4366     if (pauseExamInvalid) return;
4367     if (pausing && gameMode == IcsExamining) {
4368         if (moveNum <= pauseExamForwardMostMove) {
4369             pauseExamInvalid = TRUE;
4370             forwardMostMove = pauseExamForwardMostMove;
4371             return;
4372         }
4373     }
4374
4375   if (appData.debugMode) {
4376     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4377   }
4378     /* Parse the board */
4379     for (k = 0; k < ranks; k++) {
4380       for (j = 0; j < files; j++)
4381         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4382       if(gameInfo.holdingsWidth > 1) {
4383            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4384            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4385       }
4386     }
4387     CopyBoard(boards[moveNum], board);
4388     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4389     if (moveNum == 0) {
4390         startedFromSetupPosition =
4391           !CompareBoards(board, initialPosition);
4392         if(startedFromSetupPosition)
4393             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4394     }
4395
4396     /* [HGM] Set castling rights. Take the outermost Rooks,
4397        to make it also work for FRC opening positions. Note that board12
4398        is really defective for later FRC positions, as it has no way to
4399        indicate which Rook can castle if they are on the same side of King.
4400        For the initial position we grant rights to the outermost Rooks,
4401        and remember thos rights, and we then copy them on positions
4402        later in an FRC game. This means WB might not recognize castlings with
4403        Rooks that have moved back to their original position as illegal,
4404        but in ICS mode that is not its job anyway.
4405     */
4406     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4407     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4408
4409         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4410             if(board[0][i] == WhiteRook) j = i;
4411         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4412         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4413             if(board[0][i] == WhiteRook) j = i;
4414         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4415         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4416             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4417         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4418         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4419             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4420         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4421
4422         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4423         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4424             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4425         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4426             if(board[BOARD_HEIGHT-1][k] == bKing)
4427                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4428         if(gameInfo.variant == VariantTwoKings) {
4429             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4430             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4431             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4432         }
4433     } else { int r;
4434         r = boards[moveNum][CASTLING][0] = initialRights[0];
4435         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4436         r = boards[moveNum][CASTLING][1] = initialRights[1];
4437         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4438         r = boards[moveNum][CASTLING][3] = initialRights[3];
4439         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4440         r = boards[moveNum][CASTLING][4] = initialRights[4];
4441         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4442         /* wildcastle kludge: always assume King has rights */
4443         r = boards[moveNum][CASTLING][2] = initialRights[2];
4444         r = boards[moveNum][CASTLING][5] = initialRights[5];
4445     }
4446     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4447     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4448
4449
4450     if (ics_getting_history == H_GOT_REQ_HEADER ||
4451         ics_getting_history == H_GOT_UNREQ_HEADER) {
4452         /* This was an initial position from a move list, not
4453            the current position */
4454         return;
4455     }
4456
4457     /* Update currentMove and known move number limits */
4458     newMove = newGame || moveNum > forwardMostMove;
4459
4460     if (newGame) {
4461         forwardMostMove = backwardMostMove = currentMove = moveNum;
4462         if (gameMode == IcsExamining && moveNum == 0) {
4463           /* Workaround for ICS limitation: we are not told the wild
4464              type when starting to examine a game.  But if we ask for
4465              the move list, the move list header will tell us */
4466             ics_getting_history = H_REQUESTED;
4467             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4468             SendToICS(str);
4469         }
4470     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4471                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4472 #if ZIPPY
4473         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4474         /* [HGM] applied this also to an engine that is silently watching        */
4475         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4476             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4477             gameInfo.variant == currentlyInitializedVariant) {
4478           takeback = forwardMostMove - moveNum;
4479           for (i = 0; i < takeback; i++) {
4480             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4481             SendToProgram("undo\n", &first);
4482           }
4483         }
4484 #endif
4485
4486         forwardMostMove = moveNum;
4487         if (!pausing || currentMove > forwardMostMove)
4488           currentMove = forwardMostMove;
4489     } else {
4490         /* New part of history that is not contiguous with old part */
4491         if (pausing && gameMode == IcsExamining) {
4492             pauseExamInvalid = TRUE;
4493             forwardMostMove = pauseExamForwardMostMove;
4494             return;
4495         }
4496         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4497 #if ZIPPY
4498             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4499                 // [HGM] when we will receive the move list we now request, it will be
4500                 // fed to the engine from the first move on. So if the engine is not
4501                 // in the initial position now, bring it there.
4502                 InitChessProgram(&first, 0);
4503             }
4504 #endif
4505             ics_getting_history = H_REQUESTED;
4506             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4507             SendToICS(str);
4508         }
4509         forwardMostMove = backwardMostMove = currentMove = moveNum;
4510     }
4511
4512     /* Update the clocks */
4513     if (strchr(elapsed_time, '.')) {
4514       /* Time is in ms */
4515       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4516       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4517     } else {
4518       /* Time is in seconds */
4519       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4520       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4521     }
4522
4523
4524 #if ZIPPY
4525     if (appData.zippyPlay && newGame &&
4526         gameMode != IcsObserving && gameMode != IcsIdle &&
4527         gameMode != IcsExamining)
4528       ZippyFirstBoard(moveNum, basetime, increment);
4529 #endif
4530
4531     /* Put the move on the move list, first converting
4532        to canonical algebraic form. */
4533     if (moveNum > 0) {
4534   if (appData.debugMode) {
4535     if (appData.debugMode) { int f = forwardMostMove;
4536         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4537                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4538                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4539     }
4540     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4541     fprintf(debugFP, "moveNum = %d\n", moveNum);
4542     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4543     setbuf(debugFP, NULL);
4544   }
4545         if (moveNum <= backwardMostMove) {
4546             /* We don't know what the board looked like before
4547                this move.  Punt. */
4548           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4549             strcat(parseList[moveNum - 1], " ");
4550             strcat(parseList[moveNum - 1], elapsed_time);
4551             moveList[moveNum - 1][0] = NULLCHAR;
4552         } else if (strcmp(move_str, "none") == 0) {
4553             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4554             /* Again, we don't know what the board looked like;
4555                this is really the start of the game. */
4556             parseList[moveNum - 1][0] = NULLCHAR;
4557             moveList[moveNum - 1][0] = NULLCHAR;
4558             backwardMostMove = moveNum;
4559             startedFromSetupPosition = TRUE;
4560             fromX = fromY = toX = toY = -1;
4561         } else {
4562           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4563           //                 So we parse the long-algebraic move string in stead of the SAN move
4564           int valid; char buf[MSG_SIZ], *prom;
4565
4566           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4567                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4568           // str looks something like "Q/a1-a2"; kill the slash
4569           if(str[1] == '/')
4570             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4571           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4572           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4573                 strcat(buf, prom); // long move lacks promo specification!
4574           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4575                 if(appData.debugMode)
4576                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4577                 safeStrCpy(move_str, buf, MSG_SIZ);
4578           }
4579           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4580                                 &fromX, &fromY, &toX, &toY, &promoChar)
4581                || ParseOneMove(buf, moveNum - 1, &moveType,
4582                                 &fromX, &fromY, &toX, &toY, &promoChar);
4583           // end of long SAN patch
4584           if (valid) {
4585             (void) CoordsToAlgebraic(boards[moveNum - 1],
4586                                      PosFlags(moveNum - 1),
4587                                      fromY, fromX, toY, toX, promoChar,
4588                                      parseList[moveNum-1]);
4589             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4590               case MT_NONE:
4591               case MT_STALEMATE:
4592               default:
4593                 break;
4594               case MT_CHECK:
4595                 if(gameInfo.variant != VariantShogi)
4596                     strcat(parseList[moveNum - 1], "+");
4597                 break;
4598               case MT_CHECKMATE:
4599               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4600                 strcat(parseList[moveNum - 1], "#");
4601                 break;
4602             }
4603             strcat(parseList[moveNum - 1], " ");
4604             strcat(parseList[moveNum - 1], elapsed_time);
4605             /* currentMoveString is set as a side-effect of ParseOneMove */
4606             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4607             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4608             strcat(moveList[moveNum - 1], "\n");
4609
4610             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4611                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4612               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4613                 ChessSquare old, new = boards[moveNum][k][j];
4614                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4615                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4616                   if(old == new) continue;
4617                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4618                   else if(new == WhiteWazir || new == BlackWazir) {
4619                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4620                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4621                       else boards[moveNum][k][j] = old; // preserve type of Gold
4622                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4623                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4624               }
4625           } else {
4626             /* Move from ICS was illegal!?  Punt. */
4627             if (appData.debugMode) {
4628               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4629               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4630             }
4631             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4632             strcat(parseList[moveNum - 1], " ");
4633             strcat(parseList[moveNum - 1], elapsed_time);
4634             moveList[moveNum - 1][0] = NULLCHAR;
4635             fromX = fromY = toX = toY = -1;
4636           }
4637         }
4638   if (appData.debugMode) {
4639     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4640     setbuf(debugFP, NULL);
4641   }
4642
4643 #if ZIPPY
4644         /* Send move to chess program (BEFORE animating it). */
4645         if (appData.zippyPlay && !newGame && newMove &&
4646            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4647
4648             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4649                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4650                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4651                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4652                             move_str);
4653                     DisplayError(str, 0);
4654                 } else {
4655                     if (first.sendTime) {
4656                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4657                     }
4658                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4659                     if (firstMove && !bookHit) {
4660                         firstMove = FALSE;
4661                         if (first.useColors) {
4662                           SendToProgram(gameMode == IcsPlayingWhite ?
4663                                         "white\ngo\n" :
4664                                         "black\ngo\n", &first);
4665                         } else {
4666                           SendToProgram("go\n", &first);
4667                         }
4668                         first.maybeThinking = TRUE;
4669                     }
4670                 }
4671             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4672               if (moveList[moveNum - 1][0] == NULLCHAR) {
4673                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4674                 DisplayError(str, 0);
4675               } else {
4676                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4677                 SendMoveToProgram(moveNum - 1, &first);
4678               }
4679             }
4680         }
4681 #endif
4682     }
4683
4684     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4685         /* If move comes from a remote source, animate it.  If it
4686            isn't remote, it will have already been animated. */
4687         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4688             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4689         }
4690         if (!pausing && appData.highlightLastMove) {
4691             SetHighlights(fromX, fromY, toX, toY);
4692         }
4693     }
4694
4695     /* Start the clocks */
4696     whiteFlag = blackFlag = FALSE;
4697     appData.clockMode = !(basetime == 0 && increment == 0);
4698     if (ticking == 0) {
4699       ics_clock_paused = TRUE;
4700       StopClocks();
4701     } else if (ticking == 1) {
4702       ics_clock_paused = FALSE;
4703     }
4704     if (gameMode == IcsIdle ||
4705         relation == RELATION_OBSERVING_STATIC ||
4706         relation == RELATION_EXAMINING ||
4707         ics_clock_paused)
4708       DisplayBothClocks();
4709     else
4710       StartClocks();
4711
4712     /* Display opponents and material strengths */
4713     if (gameInfo.variant != VariantBughouse &&
4714         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4715         if (tinyLayout || smallLayout) {
4716             if(gameInfo.variant == VariantNormal)
4717               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4718                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4719                     basetime, increment);
4720             else
4721               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4722                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4723                     basetime, increment, (int) gameInfo.variant);
4724         } else {
4725             if(gameInfo.variant == VariantNormal)
4726               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4727                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4728                     basetime, increment);
4729             else
4730               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4731                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4732                     basetime, increment, VariantName(gameInfo.variant));
4733         }
4734         DisplayTitle(str);
4735   if (appData.debugMode) {
4736     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4737   }
4738     }
4739
4740
4741     /* Display the board */
4742     if (!pausing && !appData.noGUI) {
4743
4744       if (appData.premove)
4745           if (!gotPremove ||
4746              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4747              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4748               ClearPremoveHighlights();
4749
4750       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4751         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4752       DrawPosition(j, boards[currentMove]);
4753
4754       DisplayMove(moveNum - 1);
4755       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4756             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4757               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4758         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4759       }
4760     }
4761
4762     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4763 #if ZIPPY
4764     if(bookHit) { // [HGM] book: simulate book reply
4765         static char bookMove[MSG_SIZ]; // a bit generous?
4766
4767         programStats.nodes = programStats.depth = programStats.time =
4768         programStats.score = programStats.got_only_move = 0;
4769         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4770
4771         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4772         strcat(bookMove, bookHit);
4773         HandleMachineMove(bookMove, &first);
4774     }
4775 #endif
4776 }
4777
4778 void
4779 GetMoveListEvent()
4780 {
4781     char buf[MSG_SIZ];
4782     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4783         ics_getting_history = H_REQUESTED;
4784         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4785         SendToICS(buf);
4786     }
4787 }
4788
4789 void
4790 AnalysisPeriodicEvent(force)
4791      int force;
4792 {
4793     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4794          && !force) || !appData.periodicUpdates)
4795       return;
4796
4797     /* Send . command to Crafty to collect stats */
4798     SendToProgram(".\n", &first);
4799
4800     /* Don't send another until we get a response (this makes
4801        us stop sending to old Crafty's which don't understand
4802        the "." command (sending illegal cmds resets node count & time,
4803        which looks bad)) */
4804     programStats.ok_to_send = 0;
4805 }
4806
4807 void ics_update_width(new_width)
4808         int new_width;
4809 {
4810         ics_printf("set width %d\n", new_width);
4811 }
4812
4813 void
4814 SendMoveToProgram(moveNum, cps)
4815      int moveNum;
4816      ChessProgramState *cps;
4817 {
4818     char buf[MSG_SIZ];
4819
4820     if (cps->useUsermove) {
4821       SendToProgram("usermove ", cps);
4822     }
4823     if (cps->useSAN) {
4824       char *space;
4825       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4826         int len = space - parseList[moveNum];
4827         memcpy(buf, parseList[moveNum], len);
4828         buf[len++] = '\n';
4829         buf[len] = NULLCHAR;
4830       } else {
4831         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4832       }
4833       SendToProgram(buf, cps);
4834     } else {
4835       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4836         AlphaRank(moveList[moveNum], 4);
4837         SendToProgram(moveList[moveNum], cps);
4838         AlphaRank(moveList[moveNum], 4); // and back
4839       } else
4840       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4841        * the engine. It would be nice to have a better way to identify castle
4842        * moves here. */
4843       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4844                                                                          && cps->useOOCastle) {
4845         int fromX = moveList[moveNum][0] - AAA;
4846         int fromY = moveList[moveNum][1] - ONE;
4847         int toX = moveList[moveNum][2] - AAA;
4848         int toY = moveList[moveNum][3] - ONE;
4849         if((boards[moveNum][fromY][fromX] == WhiteKing
4850             && boards[moveNum][toY][toX] == WhiteRook)
4851            || (boards[moveNum][fromY][fromX] == BlackKing
4852                && boards[moveNum][toY][toX] == BlackRook)) {
4853           if(toX > fromX) SendToProgram("O-O\n", cps);
4854           else SendToProgram("O-O-O\n", cps);
4855         }
4856         else SendToProgram(moveList[moveNum], cps);
4857       }
4858       else SendToProgram(moveList[moveNum], cps);
4859       /* End of additions by Tord */
4860     }
4861
4862     /* [HGM] setting up the opening has brought engine in force mode! */
4863     /*       Send 'go' if we are in a mode where machine should play. */
4864     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4865         (gameMode == TwoMachinesPlay   ||
4866 #if ZIPPY
4867          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4868 #endif
4869          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4870         SendToProgram("go\n", cps);
4871   if (appData.debugMode) {
4872     fprintf(debugFP, "(extra)\n");
4873   }
4874     }
4875     setboardSpoiledMachineBlack = 0;
4876 }
4877
4878 void
4879 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4880      ChessMove moveType;
4881      int fromX, fromY, toX, toY;
4882      char promoChar;
4883 {
4884     char user_move[MSG_SIZ];
4885
4886     switch (moveType) {
4887       default:
4888         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4889                 (int)moveType, fromX, fromY, toX, toY);
4890         DisplayError(user_move + strlen("say "), 0);
4891         break;
4892       case WhiteKingSideCastle:
4893       case BlackKingSideCastle:
4894       case WhiteQueenSideCastleWild:
4895       case BlackQueenSideCastleWild:
4896       /* PUSH Fabien */
4897       case WhiteHSideCastleFR:
4898       case BlackHSideCastleFR:
4899       /* POP Fabien */
4900         snprintf(user_move, MSG_SIZ, "o-o\n");
4901         break;
4902       case WhiteQueenSideCastle:
4903       case BlackQueenSideCastle:
4904       case WhiteKingSideCastleWild:
4905       case BlackKingSideCastleWild:
4906       /* PUSH Fabien */
4907       case WhiteASideCastleFR:
4908       case BlackASideCastleFR:
4909       /* POP Fabien */
4910         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4911         break;
4912       case WhiteNonPromotion:
4913       case BlackNonPromotion:
4914         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4915         break;
4916       case WhitePromotion:
4917       case BlackPromotion:
4918         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4919           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4920                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4921                 PieceToChar(WhiteFerz));
4922         else if(gameInfo.variant == VariantGreat)
4923           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4924                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4925                 PieceToChar(WhiteMan));
4926         else
4927           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4928                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4929                 promoChar);
4930         break;
4931       case WhiteDrop:
4932       case BlackDrop:
4933       drop:
4934         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4935                  ToUpper(PieceToChar((ChessSquare) fromX)),
4936                  AAA + toX, ONE + toY);
4937         break;
4938       case IllegalMove:  /* could be a variant we don't quite understand */
4939         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4940       case NormalMove:
4941       case WhiteCapturesEnPassant:
4942       case BlackCapturesEnPassant:
4943         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4944                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4945         break;
4946     }
4947     SendToICS(user_move);
4948     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4949         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4950 }
4951
4952 void
4953 UploadGameEvent()
4954 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4955     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4956     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4957     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4958         DisplayError("You cannot do this while you are playing or observing", 0);
4959         return;
4960     }
4961     if(gameMode != IcsExamining) { // is this ever not the case?
4962         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4963
4964         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4965           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4966         } else { // on FICS we must first go to general examine mode
4967           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4968         }
4969         if(gameInfo.variant != VariantNormal) {
4970             // try figure out wild number, as xboard names are not always valid on ICS
4971             for(i=1; i<=36; i++) {
4972               snprintf(buf, MSG_SIZ, "wild/%d", i);
4973                 if(StringToVariant(buf) == gameInfo.variant) break;
4974             }
4975             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4976             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4977             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4978         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4979         SendToICS(ics_prefix);
4980         SendToICS(buf);
4981         if(startedFromSetupPosition || backwardMostMove != 0) {
4982           fen = PositionToFEN(backwardMostMove, NULL);
4983           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4984             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4985             SendToICS(buf);
4986           } else { // FICS: everything has to set by separate bsetup commands
4987             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4988             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4989             SendToICS(buf);
4990             if(!WhiteOnMove(backwardMostMove)) {
4991                 SendToICS("bsetup tomove black\n");
4992             }
4993             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4994             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4995             SendToICS(buf);
4996             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4997             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4998             SendToICS(buf);
4999             i = boards[backwardMostMove][EP_STATUS];
5000             if(i >= 0) { // set e.p.
5001               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5002                 SendToICS(buf);
5003             }
5004             bsetup++;
5005           }
5006         }
5007       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5008             SendToICS("bsetup done\n"); // switch to normal examining.
5009     }
5010     for(i = backwardMostMove; i<last; i++) {
5011         char buf[20];
5012         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5013         SendToICS(buf);
5014     }
5015     SendToICS(ics_prefix);
5016     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5017 }
5018
5019 void
5020 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5021      int rf, ff, rt, ft;
5022      char promoChar;
5023      char move[7];
5024 {
5025     if (rf == DROP_RANK) {
5026       sprintf(move, "%c@%c%c\n",
5027                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5028     } else {
5029         if (promoChar == 'x' || promoChar == NULLCHAR) {
5030           sprintf(move, "%c%c%c%c\n",
5031                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5032         } else {
5033             sprintf(move, "%c%c%c%c%c\n",
5034                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5035         }
5036     }
5037 }
5038
5039 void
5040 ProcessICSInitScript(f)
5041      FILE *f;
5042 {
5043     char buf[MSG_SIZ];
5044
5045     while (fgets(buf, MSG_SIZ, f)) {
5046         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5047     }
5048
5049     fclose(f);
5050 }
5051
5052
5053 static int lastX, lastY, selectFlag, dragging;
5054
5055 void
5056 Sweep(int step)
5057 {
5058     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5059     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5060     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5061     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5062     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5063     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5064     do {
5065         promoSweep -= step;
5066         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5067         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5068         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5069         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5070         if(!step) step = 1;
5071     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5072             appData.testLegality && (promoSweep == king ||
5073             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5074     ChangeDragPiece(promoSweep);
5075 }
5076
5077 int PromoScroll(int x, int y)
5078 {
5079   int step = 0;
5080
5081   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5082   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5083   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5084   if(!step) return FALSE;
5085   lastX = x; lastY = y;
5086   if((promoSweep < BlackPawn) == flipView) step = -step;
5087   if(step > 0) selectFlag = 1;
5088   if(!selectFlag) Sweep(step);
5089   return FALSE;
5090 }
5091
5092 void
5093 NextPiece(int step)
5094 {
5095     ChessSquare piece = boards[currentMove][toY][toX];
5096     do {
5097         pieceSweep -= step;
5098         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5099         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5100         if(!step) step = -1;
5101     } while(PieceToChar(pieceSweep) == '.');
5102     boards[currentMove][toY][toX] = pieceSweep;
5103     DrawPosition(FALSE, boards[currentMove]);
5104     boards[currentMove][toY][toX] = piece;
5105 }
5106 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5107 void
5108 AlphaRank(char *move, int n)
5109 {
5110 //    char *p = move, c; int x, y;
5111
5112     if (appData.debugMode) {
5113         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5114     }
5115
5116     if(move[1]=='*' &&
5117        move[2]>='0' && move[2]<='9' &&
5118        move[3]>='a' && move[3]<='x'    ) {
5119         move[1] = '@';
5120         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5121         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5122     } else
5123     if(move[0]>='0' && move[0]<='9' &&
5124        move[1]>='a' && move[1]<='x' &&
5125        move[2]>='0' && move[2]<='9' &&
5126        move[3]>='a' && move[3]<='x'    ) {
5127         /* input move, Shogi -> normal */
5128         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5129         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5130         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5131         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5132     } else
5133     if(move[1]=='@' &&
5134        move[3]>='0' && move[3]<='9' &&
5135        move[2]>='a' && move[2]<='x'    ) {
5136         move[1] = '*';
5137         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5138         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5139     } else
5140     if(
5141        move[0]>='a' && move[0]<='x' &&
5142        move[3]>='0' && move[3]<='9' &&
5143        move[2]>='a' && move[2]<='x'    ) {
5144          /* output move, normal -> Shogi */
5145         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5146         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5147         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5148         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5149         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5150     }
5151     if (appData.debugMode) {
5152         fprintf(debugFP, "   out = '%s'\n", move);
5153     }
5154 }
5155
5156 char yy_textstr[8000];
5157
5158 /* Parser for moves from gnuchess, ICS, or user typein box */
5159 Boolean
5160 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5161      char *move;
5162      int moveNum;
5163      ChessMove *moveType;
5164      int *fromX, *fromY, *toX, *toY;
5165      char *promoChar;
5166 {
5167     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5168
5169     switch (*moveType) {
5170       case WhitePromotion:
5171       case BlackPromotion:
5172       case WhiteNonPromotion:
5173       case BlackNonPromotion:
5174       case NormalMove:
5175       case WhiteCapturesEnPassant:
5176       case BlackCapturesEnPassant:
5177       case WhiteKingSideCastle:
5178       case WhiteQueenSideCastle:
5179       case BlackKingSideCastle:
5180       case BlackQueenSideCastle:
5181       case WhiteKingSideCastleWild:
5182       case WhiteQueenSideCastleWild:
5183       case BlackKingSideCastleWild:
5184       case BlackQueenSideCastleWild:
5185       /* Code added by Tord: */
5186       case WhiteHSideCastleFR:
5187       case WhiteASideCastleFR:
5188       case BlackHSideCastleFR:
5189       case BlackASideCastleFR:
5190       /* End of code added by Tord */
5191       case IllegalMove:         /* bug or odd chess variant */
5192         *fromX = currentMoveString[0] - AAA;
5193         *fromY = currentMoveString[1] - ONE;
5194         *toX = currentMoveString[2] - AAA;
5195         *toY = currentMoveString[3] - ONE;
5196         *promoChar = currentMoveString[4];
5197         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5198             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5199     if (appData.debugMode) {
5200         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5201     }
5202             *fromX = *fromY = *toX = *toY = 0;
5203             return FALSE;
5204         }
5205         if (appData.testLegality) {
5206           return (*moveType != IllegalMove);
5207         } else {
5208           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5209                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5210         }
5211
5212       case WhiteDrop:
5213       case BlackDrop:
5214         *fromX = *moveType == WhiteDrop ?
5215           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5216           (int) CharToPiece(ToLower(currentMoveString[0]));
5217         *fromY = DROP_RANK;
5218         *toX = currentMoveString[2] - AAA;
5219         *toY = currentMoveString[3] - ONE;
5220         *promoChar = NULLCHAR;
5221         return TRUE;
5222
5223       case AmbiguousMove:
5224       case ImpossibleMove:
5225       case EndOfFile:
5226       case ElapsedTime:
5227       case Comment:
5228       case PGNTag:
5229       case NAG:
5230       case WhiteWins:
5231       case BlackWins:
5232       case GameIsDrawn:
5233       default:
5234     if (appData.debugMode) {
5235         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5236     }
5237         /* bug? */
5238         *fromX = *fromY = *toX = *toY = 0;
5239         *promoChar = NULLCHAR;
5240         return FALSE;
5241     }
5242 }
5243
5244 Boolean pushed = FALSE;
5245
5246 void
5247 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5248 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5249   int fromX, fromY, toX, toY; char promoChar;
5250   ChessMove moveType;
5251   Boolean valid;
5252   int nr = 0;
5253
5254   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5255     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5256     pushed = TRUE;
5257   }
5258   endPV = forwardMostMove;
5259   do {
5260     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5261     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5262     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5263 if(appData.debugMode){
5264 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);
5265 }
5266     if(!valid && nr == 0 &&
5267        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5268         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5269         // Hande case where played move is different from leading PV move
5270         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5271         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5272         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5273         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5274           endPV += 2; // if position different, keep this
5275           moveList[endPV-1][0] = fromX + AAA;
5276           moveList[endPV-1][1] = fromY + ONE;
5277           moveList[endPV-1][2] = toX + AAA;
5278           moveList[endPV-1][3] = toY + ONE;
5279           parseList[endPV-1][0] = NULLCHAR;
5280           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5281         }
5282       }
5283     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5284     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5285     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5286     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5287         valid++; // allow comments in PV
5288         continue;
5289     }
5290     nr++;
5291     if(endPV+1 > framePtr) break; // no space, truncate
5292     if(!valid) break;
5293     endPV++;
5294     CopyBoard(boards[endPV], boards[endPV-1]);
5295     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5296     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5297     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5298     CoordsToAlgebraic(boards[endPV - 1],
5299                              PosFlags(endPV - 1),
5300                              fromY, fromX, toY, toX, promoChar,
5301                              parseList[endPV - 1]);
5302   } while(valid);
5303   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5304   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5305   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5306                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5307   DrawPosition(TRUE, boards[currentMove]);
5308 }
5309
5310 int
5311 MultiPV(ChessProgramState *cps)
5312 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5313         int i;
5314         for(i=0; i<cps->nrOptions; i++)
5315             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5316                 return i;
5317         return -1;
5318 }
5319
5320 Boolean
5321 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5322 {
5323         int startPV, multi, lineStart, origIndex = index;
5324         char *p, buf2[MSG_SIZ];
5325
5326         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5327         lastX = x; lastY = y;
5328         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5329         lineStart = startPV = index;
5330         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5331         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5332         index = startPV;
5333         do{ while(buf[index] && buf[index] != '\n') index++;
5334         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5335         buf[index] = 0;
5336         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5337                 int n = first.option[multi].value;
5338                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5339                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5340                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5341                 first.option[multi].value = n;
5342                 *start = *end = 0;
5343                 return FALSE;
5344         }
5345         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5346         *start = startPV; *end = index-1;
5347         return TRUE;
5348 }
5349
5350 Boolean
5351 LoadPV(int x, int y)
5352 { // called on right mouse click to load PV
5353   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5354   lastX = x; lastY = y;
5355   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5356   return TRUE;
5357 }
5358
5359 void
5360 UnLoadPV()
5361 {
5362   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5363   if(endPV < 0) return;
5364   endPV = -1;
5365   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5366         Boolean saveAnimate = appData.animate;
5367         if(pushed) {
5368             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5369                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5370             } else storedGames--; // abandon shelved tail of original game
5371         }
5372         pushed = FALSE;
5373         forwardMostMove = currentMove;
5374         currentMove = oldFMM;
5375         appData.animate = FALSE;
5376         ToNrEvent(forwardMostMove);
5377         appData.animate = saveAnimate;
5378   }
5379   currentMove = forwardMostMove;
5380   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5381   ClearPremoveHighlights();
5382   DrawPosition(TRUE, boards[currentMove]);
5383 }
5384
5385 void
5386 MovePV(int x, int y, int h)
5387 { // step through PV based on mouse coordinates (called on mouse move)
5388   int margin = h>>3, step = 0;
5389
5390   // we must somehow check if right button is still down (might be released off board!)
5391   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5392   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5393   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5394   if(!step) return;
5395   lastX = x; lastY = y;
5396
5397   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5398   if(endPV < 0) return;
5399   if(y < margin) step = 1; else
5400   if(y > h - margin) step = -1;
5401   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5402   currentMove += step;
5403   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5404   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5405                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5406   DrawPosition(FALSE, boards[currentMove]);
5407 }
5408
5409
5410 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5411 // All positions will have equal probability, but the current method will not provide a unique
5412 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5413 #define DARK 1
5414 #define LITE 2
5415 #define ANY 3
5416
5417 int squaresLeft[4];
5418 int piecesLeft[(int)BlackPawn];
5419 int seed, nrOfShuffles;
5420
5421 void GetPositionNumber()
5422 {       // sets global variable seed
5423         int i;
5424
5425         seed = appData.defaultFrcPosition;
5426         if(seed < 0) { // randomize based on time for negative FRC position numbers
5427                 for(i=0; i<50; i++) seed += random();
5428                 seed = random() ^ random() >> 8 ^ random() << 8;
5429                 if(seed<0) seed = -seed;
5430         }
5431 }
5432
5433 int put(Board board, int pieceType, int rank, int n, int shade)
5434 // put the piece on the (n-1)-th empty squares of the given shade
5435 {
5436         int i;
5437
5438         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5439                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5440                         board[rank][i] = (ChessSquare) pieceType;
5441                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5442                         squaresLeft[ANY]--;
5443                         piecesLeft[pieceType]--;
5444                         return i;
5445                 }
5446         }
5447         return -1;
5448 }
5449
5450
5451 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5452 // calculate where the next piece goes, (any empty square), and put it there
5453 {
5454         int i;
5455
5456         i = seed % squaresLeft[shade];
5457         nrOfShuffles *= squaresLeft[shade];
5458         seed /= squaresLeft[shade];
5459         put(board, pieceType, rank, i, shade);
5460 }
5461
5462 void AddTwoPieces(Board board, int pieceType, int rank)
5463 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5464 {
5465         int i, n=squaresLeft[ANY], j=n-1, k;
5466
5467         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5468         i = seed % k;  // pick one
5469         nrOfShuffles *= k;
5470         seed /= k;
5471         while(i >= j) i -= j--;
5472         j = n - 1 - j; i += j;
5473         put(board, pieceType, rank, j, ANY);
5474         put(board, pieceType, rank, i, ANY);
5475 }
5476
5477 void SetUpShuffle(Board board, int number)
5478 {
5479         int i, p, first=1;
5480
5481         GetPositionNumber(); nrOfShuffles = 1;
5482
5483         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5484         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5485         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5486
5487         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5488
5489         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5490             p = (int) board[0][i];
5491             if(p < (int) BlackPawn) piecesLeft[p] ++;
5492             board[0][i] = EmptySquare;
5493         }
5494
5495         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5496             // shuffles restricted to allow normal castling put KRR first
5497             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5498                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5499             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5500                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5501             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5502                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5503             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5504                 put(board, WhiteRook, 0, 0, ANY);
5505             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5506         }
5507
5508         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5509             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5510             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5511                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5512                 while(piecesLeft[p] >= 2) {
5513                     AddOnePiece(board, p, 0, LITE);
5514                     AddOnePiece(board, p, 0, DARK);
5515                 }
5516                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5517             }
5518
5519         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5520             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5521             // but we leave King and Rooks for last, to possibly obey FRC restriction
5522             if(p == (int)WhiteRook) continue;
5523             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5524             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5525         }
5526
5527         // now everything is placed, except perhaps King (Unicorn) and Rooks
5528
5529         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5530             // Last King gets castling rights
5531             while(piecesLeft[(int)WhiteUnicorn]) {
5532                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5533                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5534             }
5535
5536             while(piecesLeft[(int)WhiteKing]) {
5537                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5538                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5539             }
5540
5541
5542         } else {
5543             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5544             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5545         }
5546
5547         // Only Rooks can be left; simply place them all
5548         while(piecesLeft[(int)WhiteRook]) {
5549                 i = put(board, WhiteRook, 0, 0, ANY);
5550                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5551                         if(first) {
5552                                 first=0;
5553                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5554                         }
5555                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5556                 }
5557         }
5558         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5559             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5560         }
5561
5562         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5563 }
5564
5565 int SetCharTable( char *table, const char * map )
5566 /* [HGM] moved here from winboard.c because of its general usefulness */
5567 /*       Basically a safe strcpy that uses the last character as King */
5568 {
5569     int result = FALSE; int NrPieces;
5570
5571     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5572                     && NrPieces >= 12 && !(NrPieces&1)) {
5573         int i; /* [HGM] Accept even length from 12 to 34 */
5574
5575         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5576         for( i=0; i<NrPieces/2-1; i++ ) {
5577             table[i] = map[i];
5578             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5579         }
5580         table[(int) WhiteKing]  = map[NrPieces/2-1];
5581         table[(int) BlackKing]  = map[NrPieces-1];
5582
5583         result = TRUE;
5584     }
5585
5586     return result;
5587 }
5588
5589 void Prelude(Board board)
5590 {       // [HGM] superchess: random selection of exo-pieces
5591         int i, j, k; ChessSquare p;
5592         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5593
5594         GetPositionNumber(); // use FRC position number
5595
5596         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5597             SetCharTable(pieceToChar, appData.pieceToCharTable);
5598             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5599                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5600         }
5601
5602         j = seed%4;                 seed /= 4;
5603         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5604         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5605         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5606         j = seed%3 + (seed%3 >= j); seed /= 3;
5607         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5608         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5609         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5610         j = seed%3;                 seed /= 3;
5611         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5612         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5613         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5614         j = seed%2 + (seed%2 >= j); seed /= 2;
5615         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5616         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5617         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5618         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5619         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5620         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5621         put(board, exoPieces[0],    0, 0, ANY);
5622         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5623 }
5624
5625 void
5626 InitPosition(redraw)
5627      int redraw;
5628 {
5629     ChessSquare (* pieces)[BOARD_FILES];
5630     int i, j, pawnRow, overrule,
5631     oldx = gameInfo.boardWidth,
5632     oldy = gameInfo.boardHeight,
5633     oldh = gameInfo.holdingsWidth;
5634     static int oldv;
5635
5636     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5637
5638     /* [AS] Initialize pv info list [HGM] and game status */
5639     {
5640         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5641             pvInfoList[i].depth = 0;
5642             boards[i][EP_STATUS] = EP_NONE;
5643             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5644         }
5645
5646         initialRulePlies = 0; /* 50-move counter start */
5647
5648         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5649         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5650     }
5651
5652
5653     /* [HGM] logic here is completely changed. In stead of full positions */
5654     /* the initialized data only consist of the two backranks. The switch */
5655     /* selects which one we will use, which is than copied to the Board   */
5656     /* initialPosition, which for the rest is initialized by Pawns and    */
5657     /* empty squares. This initial position is then copied to boards[0],  */
5658     /* possibly after shuffling, so that it remains available.            */
5659
5660     gameInfo.holdingsWidth = 0; /* default board sizes */
5661     gameInfo.boardWidth    = 8;
5662     gameInfo.boardHeight   = 8;
5663     gameInfo.holdingsSize  = 0;
5664     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5665     for(i=0; i<BOARD_FILES-2; i++)
5666       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5667     initialPosition[EP_STATUS] = EP_NONE;
5668     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5669     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5670          SetCharTable(pieceNickName, appData.pieceNickNames);
5671     else SetCharTable(pieceNickName, "............");
5672     pieces = FIDEArray;
5673
5674     switch (gameInfo.variant) {
5675     case VariantFischeRandom:
5676       shuffleOpenings = TRUE;
5677     default:
5678       break;
5679     case VariantShatranj:
5680       pieces = ShatranjArray;
5681       nrCastlingRights = 0;
5682       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5683       break;
5684     case VariantMakruk:
5685       pieces = makrukArray;
5686       nrCastlingRights = 0;
5687       startedFromSetupPosition = TRUE;
5688       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5689       break;
5690     case VariantTwoKings:
5691       pieces = twoKingsArray;
5692       break;
5693     case VariantCapaRandom:
5694       shuffleOpenings = TRUE;
5695     case VariantCapablanca:
5696       pieces = CapablancaArray;
5697       gameInfo.boardWidth = 10;
5698       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5699       break;
5700     case VariantGothic:
5701       pieces = GothicArray;
5702       gameInfo.boardWidth = 10;
5703       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5704       break;
5705     case VariantSChess:
5706       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5707       gameInfo.holdingsSize = 7;
5708       break;
5709     case VariantJanus:
5710       pieces = JanusArray;
5711       gameInfo.boardWidth = 10;
5712       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5713       nrCastlingRights = 6;
5714         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5715         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5716         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5717         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5718         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5719         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5720       break;
5721     case VariantFalcon:
5722       pieces = FalconArray;
5723       gameInfo.boardWidth = 10;
5724       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5725       break;
5726     case VariantXiangqi:
5727       pieces = XiangqiArray;
5728       gameInfo.boardWidth  = 9;
5729       gameInfo.boardHeight = 10;
5730       nrCastlingRights = 0;
5731       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5732       break;
5733     case VariantShogi:
5734       pieces = ShogiArray;
5735       gameInfo.boardWidth  = 9;
5736       gameInfo.boardHeight = 9;
5737       gameInfo.holdingsSize = 7;
5738       nrCastlingRights = 0;
5739       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5740       break;
5741     case VariantCourier:
5742       pieces = CourierArray;
5743       gameInfo.boardWidth  = 12;
5744       nrCastlingRights = 0;
5745       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5746       break;
5747     case VariantKnightmate:
5748       pieces = KnightmateArray;
5749       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5750       break;
5751     case VariantSpartan:
5752       pieces = SpartanArray;
5753       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5754       break;
5755     case VariantFairy:
5756       pieces = fairyArray;
5757       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5758       break;
5759     case VariantGreat:
5760       pieces = GreatArray;
5761       gameInfo.boardWidth = 10;
5762       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5763       gameInfo.holdingsSize = 8;
5764       break;
5765     case VariantSuper:
5766       pieces = FIDEArray;
5767       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5768       gameInfo.holdingsSize = 8;
5769       startedFromSetupPosition = TRUE;
5770       break;
5771     case VariantCrazyhouse:
5772     case VariantBughouse:
5773       pieces = FIDEArray;
5774       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5775       gameInfo.holdingsSize = 5;
5776       break;
5777     case VariantWildCastle:
5778       pieces = FIDEArray;
5779       /* !!?shuffle with kings guaranteed to be on d or e file */
5780       shuffleOpenings = 1;
5781       break;
5782     case VariantNoCastle:
5783       pieces = FIDEArray;
5784       nrCastlingRights = 0;
5785       /* !!?unconstrained back-rank shuffle */
5786       shuffleOpenings = 1;
5787       break;
5788     }
5789
5790     overrule = 0;
5791     if(appData.NrFiles >= 0) {
5792         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5793         gameInfo.boardWidth = appData.NrFiles;
5794     }
5795     if(appData.NrRanks >= 0) {
5796         gameInfo.boardHeight = appData.NrRanks;
5797     }
5798     if(appData.holdingsSize >= 0) {
5799         i = appData.holdingsSize;
5800         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5801         gameInfo.holdingsSize = i;
5802     }
5803     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5804     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5805         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5806
5807     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5808     if(pawnRow < 1) pawnRow = 1;
5809     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5810
5811     /* User pieceToChar list overrules defaults */
5812     if(appData.pieceToCharTable != NULL)
5813         SetCharTable(pieceToChar, appData.pieceToCharTable);
5814
5815     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5816
5817         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5818             s = (ChessSquare) 0; /* account holding counts in guard band */
5819         for( i=0; i<BOARD_HEIGHT; i++ )
5820             initialPosition[i][j] = s;
5821
5822         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5823         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5824         initialPosition[pawnRow][j] = WhitePawn;
5825         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5826         if(gameInfo.variant == VariantXiangqi) {
5827             if(j&1) {
5828                 initialPosition[pawnRow][j] =
5829                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5830                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5831                    initialPosition[2][j] = WhiteCannon;
5832                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5833                 }
5834             }
5835         }
5836         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5837     }
5838     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5839
5840             j=BOARD_LEFT+1;
5841             initialPosition[1][j] = WhiteBishop;
5842             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5843             j=BOARD_RGHT-2;
5844             initialPosition[1][j] = WhiteRook;
5845             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5846     }
5847
5848     if( nrCastlingRights == -1) {
5849         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5850         /*       This sets default castling rights from none to normal corners   */
5851         /* Variants with other castling rights must set them themselves above    */
5852         nrCastlingRights = 6;
5853
5854         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5855         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5856         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5857         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5858         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5859         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5860      }
5861
5862      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5863      if(gameInfo.variant == VariantGreat) { // promotion commoners
5864         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5865         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5866         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5867         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5868      }
5869      if( gameInfo.variant == VariantSChess ) {
5870       initialPosition[1][0] = BlackMarshall;
5871       initialPosition[2][0] = BlackAngel;
5872       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5873       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5874       initialPosition[1][1] = initialPosition[2][1] = 
5875       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5876      }
5877   if (appData.debugMode) {
5878     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5879   }
5880     if(shuffleOpenings) {
5881         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5882         startedFromSetupPosition = TRUE;
5883     }
5884     if(startedFromPositionFile) {
5885       /* [HGM] loadPos: use PositionFile for every new game */
5886       CopyBoard(initialPosition, filePosition);
5887       for(i=0; i<nrCastlingRights; i++)
5888           initialRights[i] = filePosition[CASTLING][i];
5889       startedFromSetupPosition = TRUE;
5890     }
5891
5892     CopyBoard(boards[0], initialPosition);
5893
5894     if(oldx != gameInfo.boardWidth ||
5895        oldy != gameInfo.boardHeight ||
5896        oldv != gameInfo.variant ||
5897        oldh != gameInfo.holdingsWidth
5898                                          )
5899             InitDrawingSizes(-2 ,0);
5900
5901     oldv = gameInfo.variant;
5902     if (redraw)
5903       DrawPosition(TRUE, boards[currentMove]);
5904 }
5905
5906 void
5907 SendBoard(cps, moveNum)
5908      ChessProgramState *cps;
5909      int moveNum;
5910 {
5911     char message[MSG_SIZ];
5912
5913     if (cps->useSetboard) {
5914       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5915       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5916       SendToProgram(message, cps);
5917       free(fen);
5918
5919     } else {
5920       ChessSquare *bp;
5921       int i, j;
5922       /* Kludge to set black to move, avoiding the troublesome and now
5923        * deprecated "black" command.
5924        */
5925       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5926         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5927
5928       SendToProgram("edit\n", cps);
5929       SendToProgram("#\n", cps);
5930       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5931         bp = &boards[moveNum][i][BOARD_LEFT];
5932         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5933           if ((int) *bp < (int) BlackPawn) {
5934             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5935                     AAA + j, ONE + i);
5936             if(message[0] == '+' || message[0] == '~') {
5937               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5938                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5939                         AAA + j, ONE + i);
5940             }
5941             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5942                 message[1] = BOARD_RGHT   - 1 - j + '1';
5943                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5944             }
5945             SendToProgram(message, cps);
5946           }
5947         }
5948       }
5949
5950       SendToProgram("c\n", cps);
5951       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5952         bp = &boards[moveNum][i][BOARD_LEFT];
5953         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5954           if (((int) *bp != (int) EmptySquare)
5955               && ((int) *bp >= (int) BlackPawn)) {
5956             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5957                     AAA + j, ONE + i);
5958             if(message[0] == '+' || message[0] == '~') {
5959               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5960                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5961                         AAA + j, ONE + i);
5962             }
5963             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5964                 message[1] = BOARD_RGHT   - 1 - j + '1';
5965                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5966             }
5967             SendToProgram(message, cps);
5968           }
5969         }
5970       }
5971
5972       SendToProgram(".\n", cps);
5973     }
5974     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5975 }
5976
5977 ChessSquare
5978 DefaultPromoChoice(int white)
5979 {
5980     ChessSquare result;
5981     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5982         result = WhiteFerz; // no choice
5983     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5984         result= WhiteKing; // in Suicide Q is the last thing we want
5985     else if(gameInfo.variant == VariantSpartan)
5986         result = white ? WhiteQueen : WhiteAngel;
5987     else result = WhiteQueen;
5988     if(!white) result = WHITE_TO_BLACK result;
5989     return result;
5990 }
5991
5992 static int autoQueen; // [HGM] oneclick
5993
5994 int
5995 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5996 {
5997     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5998     /* [HGM] add Shogi promotions */
5999     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6000     ChessSquare piece;
6001     ChessMove moveType;
6002     Boolean premove;
6003
6004     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6005     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6006
6007     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6008       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6009         return FALSE;
6010
6011     piece = boards[currentMove][fromY][fromX];
6012     if(gameInfo.variant == VariantShogi) {
6013         promotionZoneSize = BOARD_HEIGHT/3;
6014         highestPromotingPiece = (int)WhiteFerz;
6015     } else if(gameInfo.variant == VariantMakruk) {
6016         promotionZoneSize = 3;
6017     }
6018
6019     // Treat Lance as Pawn when it is not representing Amazon
6020     if(gameInfo.variant != VariantSuper) {
6021         if(piece == WhiteLance) piece = WhitePawn; else
6022         if(piece == BlackLance) piece = BlackPawn;
6023     }
6024
6025     // next weed out all moves that do not touch the promotion zone at all
6026     if((int)piece >= BlackPawn) {
6027         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6028              return FALSE;
6029         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6030     } else {
6031         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6032            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6033     }
6034
6035     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6036
6037     // weed out mandatory Shogi promotions
6038     if(gameInfo.variant == VariantShogi) {
6039         if(piece >= BlackPawn) {
6040             if(toY == 0 && piece == BlackPawn ||
6041                toY == 0 && piece == BlackQueen ||
6042                toY <= 1 && piece == BlackKnight) {
6043                 *promoChoice = '+';
6044                 return FALSE;
6045             }
6046         } else {
6047             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6048                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6049                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6050                 *promoChoice = '+';
6051                 return FALSE;
6052             }
6053         }
6054     }
6055
6056     // weed out obviously illegal Pawn moves
6057     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6058         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6059         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6060         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6061         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6062         // note we are not allowed to test for valid (non-)capture, due to premove
6063     }
6064
6065     // we either have a choice what to promote to, or (in Shogi) whether to promote
6066     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6067         *promoChoice = PieceToChar(BlackFerz);  // no choice
6068         return FALSE;
6069     }
6070     // no sense asking what we must promote to if it is going to explode...
6071     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6072         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6073         return FALSE;
6074     }
6075     // give caller the default choice even if we will not make it
6076     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6077     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6078     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6079                            && gameInfo.variant != VariantShogi
6080                            && gameInfo.variant != VariantSuper) return FALSE;
6081     if(autoQueen) return FALSE; // predetermined
6082
6083     // suppress promotion popup on illegal moves that are not premoves
6084     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6085               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6086     if(appData.testLegality && !premove) {
6087         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6088                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6089         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6090             return FALSE;
6091     }
6092
6093     return TRUE;
6094 }
6095
6096 int
6097 InPalace(row, column)
6098      int row, column;
6099 {   /* [HGM] for Xiangqi */
6100     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6101          column < (BOARD_WIDTH + 4)/2 &&
6102          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6103     return FALSE;
6104 }
6105
6106 int
6107 PieceForSquare (x, y)
6108      int x;
6109      int y;
6110 {
6111   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6112      return -1;
6113   else
6114      return boards[currentMove][y][x];
6115 }
6116
6117 int
6118 OKToStartUserMove(x, y)
6119      int x, y;
6120 {
6121     ChessSquare from_piece;
6122     int white_piece;
6123
6124     if (matchMode) return FALSE;
6125     if (gameMode == EditPosition) return TRUE;
6126
6127     if (x >= 0 && y >= 0)
6128       from_piece = boards[currentMove][y][x];
6129     else
6130       from_piece = EmptySquare;
6131
6132     if (from_piece == EmptySquare) return FALSE;
6133
6134     white_piece = (int)from_piece >= (int)WhitePawn &&
6135       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6136
6137     switch (gameMode) {
6138       case PlayFromGameFile:
6139       case AnalyzeFile:
6140       case TwoMachinesPlay:
6141       case EndOfGame:
6142         return FALSE;
6143
6144       case IcsObserving:
6145       case IcsIdle:
6146         return FALSE;
6147
6148       case MachinePlaysWhite:
6149       case IcsPlayingBlack:
6150         if (appData.zippyPlay) return FALSE;
6151         if (white_piece) {
6152             DisplayMoveError(_("You are playing Black"));
6153             return FALSE;
6154         }
6155         break;
6156
6157       case MachinePlaysBlack:
6158       case IcsPlayingWhite:
6159         if (appData.zippyPlay) return FALSE;
6160         if (!white_piece) {
6161             DisplayMoveError(_("You are playing White"));
6162             return FALSE;
6163         }
6164         break;
6165
6166       case EditGame:
6167         if (!white_piece && WhiteOnMove(currentMove)) {
6168             DisplayMoveError(_("It is White's turn"));
6169             return FALSE;
6170         }
6171         if (white_piece && !WhiteOnMove(currentMove)) {
6172             DisplayMoveError(_("It is Black's turn"));
6173             return FALSE;
6174         }
6175         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6176             /* Editing correspondence game history */
6177             /* Could disallow this or prompt for confirmation */
6178             cmailOldMove = -1;
6179         }
6180         break;
6181
6182       case BeginningOfGame:
6183         if (appData.icsActive) return FALSE;
6184         if (!appData.noChessProgram) {
6185             if (!white_piece) {
6186                 DisplayMoveError(_("You are playing White"));
6187                 return FALSE;
6188             }
6189         }
6190         break;
6191
6192       case Training:
6193         if (!white_piece && WhiteOnMove(currentMove)) {
6194             DisplayMoveError(_("It is White's turn"));
6195             return FALSE;
6196         }
6197         if (white_piece && !WhiteOnMove(currentMove)) {
6198             DisplayMoveError(_("It is Black's turn"));
6199             return FALSE;
6200         }
6201         break;
6202
6203       default:
6204       case IcsExamining:
6205         break;
6206     }
6207     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6208         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6209         && gameMode != AnalyzeFile && gameMode != Training) {
6210         DisplayMoveError(_("Displayed position is not current"));
6211         return FALSE;
6212     }
6213     return TRUE;
6214 }
6215
6216 Boolean
6217 OnlyMove(int *x, int *y, Boolean captures) {
6218     DisambiguateClosure cl;
6219     if (appData.zippyPlay) return FALSE;
6220     switch(gameMode) {
6221       case MachinePlaysBlack:
6222       case IcsPlayingWhite:
6223       case BeginningOfGame:
6224         if(!WhiteOnMove(currentMove)) return FALSE;
6225         break;
6226       case MachinePlaysWhite:
6227       case IcsPlayingBlack:
6228         if(WhiteOnMove(currentMove)) return FALSE;
6229         break;
6230       case EditGame:
6231         break;
6232       default:
6233         return FALSE;
6234     }
6235     cl.pieceIn = EmptySquare;
6236     cl.rfIn = *y;
6237     cl.ffIn = *x;
6238     cl.rtIn = -1;
6239     cl.ftIn = -1;
6240     cl.promoCharIn = NULLCHAR;
6241     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6242     if( cl.kind == NormalMove ||
6243         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6244         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6245         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6246       fromX = cl.ff;
6247       fromY = cl.rf;
6248       *x = cl.ft;
6249       *y = cl.rt;
6250       return TRUE;
6251     }
6252     if(cl.kind != ImpossibleMove) return FALSE;
6253     cl.pieceIn = EmptySquare;
6254     cl.rfIn = -1;
6255     cl.ffIn = -1;
6256     cl.rtIn = *y;
6257     cl.ftIn = *x;
6258     cl.promoCharIn = NULLCHAR;
6259     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6260     if( cl.kind == NormalMove ||
6261         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6262         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6263         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6264       fromX = cl.ff;
6265       fromY = cl.rf;
6266       *x = cl.ft;
6267       *y = cl.rt;
6268       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6269       return TRUE;
6270     }
6271     return FALSE;
6272 }
6273
6274 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6275 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6276 int lastLoadGameUseList = FALSE;
6277 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6278 ChessMove lastLoadGameStart = EndOfFile;
6279
6280 void
6281 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6282      int fromX, fromY, toX, toY;
6283      int promoChar;
6284 {
6285     ChessMove moveType;
6286     ChessSquare pdown, pup;
6287
6288     /* Check if the user is playing in turn.  This is complicated because we
6289        let the user "pick up" a piece before it is his turn.  So the piece he
6290        tried to pick up may have been captured by the time he puts it down!
6291        Therefore we use the color the user is supposed to be playing in this
6292        test, not the color of the piece that is currently on the starting
6293        square---except in EditGame mode, where the user is playing both
6294        sides; fortunately there the capture race can't happen.  (It can
6295        now happen in IcsExamining mode, but that's just too bad.  The user
6296        will get a somewhat confusing message in that case.)
6297        */
6298
6299     switch (gameMode) {
6300       case PlayFromGameFile:
6301       case AnalyzeFile:
6302       case TwoMachinesPlay:
6303       case EndOfGame:
6304       case IcsObserving:
6305       case IcsIdle:
6306         /* We switched into a game mode where moves are not accepted,
6307            perhaps while the mouse button was down. */
6308         return;
6309
6310       case MachinePlaysWhite:
6311         /* User is moving for Black */
6312         if (WhiteOnMove(currentMove)) {
6313             DisplayMoveError(_("It is White's turn"));
6314             return;
6315         }
6316         break;
6317
6318       case MachinePlaysBlack:
6319         /* User is moving for White */
6320         if (!WhiteOnMove(currentMove)) {
6321             DisplayMoveError(_("It is Black's turn"));
6322             return;
6323         }
6324         break;
6325
6326       case EditGame:
6327       case IcsExamining:
6328       case BeginningOfGame:
6329       case AnalyzeMode:
6330       case Training:
6331         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6332         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6333             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6334             /* User is moving for Black */
6335             if (WhiteOnMove(currentMove)) {
6336                 DisplayMoveError(_("It is White's turn"));
6337                 return;
6338             }
6339         } else {
6340             /* User is moving for White */
6341             if (!WhiteOnMove(currentMove)) {
6342                 DisplayMoveError(_("It is Black's turn"));
6343                 return;
6344             }
6345         }
6346         break;
6347
6348       case IcsPlayingBlack:
6349         /* User is moving for Black */
6350         if (WhiteOnMove(currentMove)) {
6351             if (!appData.premove) {
6352                 DisplayMoveError(_("It is White's turn"));
6353             } else if (toX >= 0 && toY >= 0) {
6354                 premoveToX = toX;
6355                 premoveToY = toY;
6356                 premoveFromX = fromX;
6357                 premoveFromY = fromY;
6358                 premovePromoChar = promoChar;
6359                 gotPremove = 1;
6360                 if (appData.debugMode)
6361                     fprintf(debugFP, "Got premove: fromX %d,"
6362                             "fromY %d, toX %d, toY %d\n",
6363                             fromX, fromY, toX, toY);
6364             }
6365             return;
6366         }
6367         break;
6368
6369       case IcsPlayingWhite:
6370         /* User is moving for White */
6371         if (!WhiteOnMove(currentMove)) {
6372             if (!appData.premove) {
6373                 DisplayMoveError(_("It is Black's turn"));
6374             } else if (toX >= 0 && toY >= 0) {
6375                 premoveToX = toX;
6376                 premoveToY = toY;
6377                 premoveFromX = fromX;
6378                 premoveFromY = fromY;
6379                 premovePromoChar = promoChar;
6380                 gotPremove = 1;
6381                 if (appData.debugMode)
6382                     fprintf(debugFP, "Got premove: fromX %d,"
6383                             "fromY %d, toX %d, toY %d\n",
6384                             fromX, fromY, toX, toY);
6385             }
6386             return;
6387         }
6388         break;
6389
6390       default:
6391         break;
6392
6393       case EditPosition:
6394         /* EditPosition, empty square, or different color piece;
6395            click-click move is possible */
6396         if (toX == -2 || toY == -2) {
6397             boards[0][fromY][fromX] = EmptySquare;
6398             DrawPosition(FALSE, boards[currentMove]);
6399             return;
6400         } else if (toX >= 0 && toY >= 0) {
6401             boards[0][toY][toX] = boards[0][fromY][fromX];
6402             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6403                 if(boards[0][fromY][0] != EmptySquare) {
6404                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6405                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6406                 }
6407             } else
6408             if(fromX == BOARD_RGHT+1) {
6409                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6410                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6411                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6412                 }
6413             } else
6414             boards[0][fromY][fromX] = EmptySquare;
6415             DrawPosition(FALSE, boards[currentMove]);
6416             return;
6417         }
6418         return;
6419     }
6420
6421     if(toX < 0 || toY < 0) return;
6422     pdown = boards[currentMove][fromY][fromX];
6423     pup = boards[currentMove][toY][toX];
6424
6425     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6426     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6427          if( pup != EmptySquare ) return;
6428          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6429            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6430                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6431            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6432            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6433            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6434            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6435          fromY = DROP_RANK;
6436     }
6437
6438     /* [HGM] always test for legality, to get promotion info */
6439     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6440                                          fromY, fromX, toY, toX, promoChar);
6441     /* [HGM] but possibly ignore an IllegalMove result */
6442     if (appData.testLegality) {
6443         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6444             DisplayMoveError(_("Illegal move"));
6445             return;
6446         }
6447     }
6448
6449     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6450 }
6451
6452 /* Common tail of UserMoveEvent and DropMenuEvent */
6453 int
6454 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6455      ChessMove moveType;
6456      int fromX, fromY, toX, toY;
6457      /*char*/int promoChar;
6458 {
6459     char *bookHit = 0;
6460
6461     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6462         // [HGM] superchess: suppress promotions to non-available piece
6463         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6464         if(WhiteOnMove(currentMove)) {
6465             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6466         } else {
6467             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6468         }
6469     }
6470
6471     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6472        move type in caller when we know the move is a legal promotion */
6473     if(moveType == NormalMove && promoChar)
6474         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6475
6476     /* [HGM] <popupFix> The following if has been moved here from
6477        UserMoveEvent(). Because it seemed to belong here (why not allow
6478        piece drops in training games?), and because it can only be
6479        performed after it is known to what we promote. */
6480     if (gameMode == Training) {
6481       /* compare the move played on the board to the next move in the
6482        * game. If they match, display the move and the opponent's response.
6483        * If they don't match, display an error message.
6484        */
6485       int saveAnimate;
6486       Board testBoard;
6487       CopyBoard(testBoard, boards[currentMove]);
6488       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6489
6490       if (CompareBoards(testBoard, boards[currentMove+1])) {
6491         ForwardInner(currentMove+1);
6492
6493         /* Autoplay the opponent's response.
6494          * if appData.animate was TRUE when Training mode was entered,
6495          * the response will be animated.
6496          */
6497         saveAnimate = appData.animate;
6498         appData.animate = animateTraining;
6499         ForwardInner(currentMove+1);
6500         appData.animate = saveAnimate;
6501
6502         /* check for the end of the game */
6503         if (currentMove >= forwardMostMove) {
6504           gameMode = PlayFromGameFile;
6505           ModeHighlight();
6506           SetTrainingModeOff();
6507           DisplayInformation(_("End of game"));
6508         }
6509       } else {
6510         DisplayError(_("Incorrect move"), 0);
6511       }
6512       return 1;
6513     }
6514
6515   /* Ok, now we know that the move is good, so we can kill
6516      the previous line in Analysis Mode */
6517   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6518                                 && currentMove < forwardMostMove) {
6519     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6520     else forwardMostMove = currentMove;
6521   }
6522
6523   /* If we need the chess program but it's dead, restart it */
6524   ResurrectChessProgram();
6525
6526   /* A user move restarts a paused game*/
6527   if (pausing)
6528     PauseEvent();
6529
6530   thinkOutput[0] = NULLCHAR;
6531
6532   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6533
6534   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6535     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6536     return 1;
6537   }
6538
6539   if (gameMode == BeginningOfGame) {
6540     if (appData.noChessProgram) {
6541       gameMode = EditGame;
6542       SetGameInfo();
6543     } else {
6544       char buf[MSG_SIZ];
6545       gameMode = MachinePlaysBlack;
6546       StartClocks();
6547       SetGameInfo();
6548       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6549       DisplayTitle(buf);
6550       if (first.sendName) {
6551         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6552         SendToProgram(buf, &first);
6553       }
6554       StartClocks();
6555     }
6556     ModeHighlight();
6557   }
6558
6559   /* Relay move to ICS or chess engine */
6560   if (appData.icsActive) {
6561     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6562         gameMode == IcsExamining) {
6563       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6564         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6565         SendToICS("draw ");
6566         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6567       }
6568       // also send plain move, in case ICS does not understand atomic claims
6569       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6570       ics_user_moved = 1;
6571     }
6572   } else {
6573     if (first.sendTime && (gameMode == BeginningOfGame ||
6574                            gameMode == MachinePlaysWhite ||
6575                            gameMode == MachinePlaysBlack)) {
6576       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6577     }
6578     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6579          // [HGM] book: if program might be playing, let it use book
6580         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6581         first.maybeThinking = TRUE;
6582     } else SendMoveToProgram(forwardMostMove-1, &first);
6583     if (currentMove == cmailOldMove + 1) {
6584       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6585     }
6586   }
6587
6588   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6589
6590   switch (gameMode) {
6591   case EditGame:
6592     if(appData.testLegality)
6593     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6594     case MT_NONE:
6595     case MT_CHECK:
6596       break;
6597     case MT_CHECKMATE:
6598     case MT_STAINMATE:
6599       if (WhiteOnMove(currentMove)) {
6600         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6601       } else {
6602         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6603       }
6604       break;
6605     case MT_STALEMATE:
6606       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6607       break;
6608     }
6609     break;
6610
6611   case MachinePlaysBlack:
6612   case MachinePlaysWhite:
6613     /* disable certain menu options while machine is thinking */
6614     SetMachineThinkingEnables();
6615     break;
6616
6617   default:
6618     break;
6619   }
6620
6621   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6622   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6623
6624   if(bookHit) { // [HGM] book: simulate book reply
6625         static char bookMove[MSG_SIZ]; // a bit generous?
6626
6627         programStats.nodes = programStats.depth = programStats.time =
6628         programStats.score = programStats.got_only_move = 0;
6629         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6630
6631         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6632         strcat(bookMove, bookHit);
6633         HandleMachineMove(bookMove, &first);
6634   }
6635   return 1;
6636 }
6637
6638 void
6639 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6640      Board board;
6641      int flags;
6642      ChessMove kind;
6643      int rf, ff, rt, ft;
6644      VOIDSTAR closure;
6645 {
6646     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6647     Markers *m = (Markers *) closure;
6648     if(rf == fromY && ff == fromX)
6649         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6650                          || kind == WhiteCapturesEnPassant
6651                          || kind == BlackCapturesEnPassant);
6652     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6653 }
6654
6655 void
6656 MarkTargetSquares(int clear)
6657 {
6658   int x, y;
6659   if(!appData.markers || !appData.highlightDragging ||
6660      !appData.testLegality || gameMode == EditPosition) return;
6661   if(clear) {
6662     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6663   } else {
6664     int capt = 0;
6665     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6666     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6667       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6668       if(capt)
6669       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6670     }
6671   }
6672   DrawPosition(TRUE, NULL);
6673 }
6674
6675 int
6676 Explode(Board board, int fromX, int fromY, int toX, int toY)
6677 {
6678     if(gameInfo.variant == VariantAtomic &&
6679        (board[toY][toX] != EmptySquare ||                     // capture?
6680         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6681                          board[fromY][fromX] == BlackPawn   )
6682       )) {
6683         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6684         return TRUE;
6685     }
6686     return FALSE;
6687 }
6688
6689 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6690
6691 int CanPromote(ChessSquare piece, int y)
6692 {
6693         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6694         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6695         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6696            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6697            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6698                                                   gameInfo.variant == VariantMakruk) return FALSE;
6699         return (piece == BlackPawn && y == 1 ||
6700                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6701                 piece == BlackLance && y == 1 ||
6702                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6703 }
6704
6705 void LeftClick(ClickType clickType, int xPix, int yPix)
6706 {
6707     int x, y;
6708     Boolean saveAnimate;
6709     static int second = 0, promotionChoice = 0, clearFlag = 0;
6710     char promoChoice = NULLCHAR;
6711     ChessSquare piece;
6712
6713     if(appData.seekGraph && appData.icsActive && loggedOn &&
6714         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6715         SeekGraphClick(clickType, xPix, yPix, 0);
6716         return;
6717     }
6718
6719     if (clickType == Press) ErrorPopDown();
6720     MarkTargetSquares(1);
6721
6722     x = EventToSquare(xPix, BOARD_WIDTH);
6723     y = EventToSquare(yPix, BOARD_HEIGHT);
6724     if (!flipView && y >= 0) {
6725         y = BOARD_HEIGHT - 1 - y;
6726     }
6727     if (flipView && x >= 0) {
6728         x = BOARD_WIDTH - 1 - x;
6729     }
6730
6731     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6732         defaultPromoChoice = promoSweep;
6733         promoSweep = EmptySquare;   // terminate sweep
6734         promoDefaultAltered = TRUE;
6735         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6736     }
6737
6738     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6739         if(clickType == Release) return; // ignore upclick of click-click destination
6740         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6741         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6742         if(gameInfo.holdingsWidth &&
6743                 (WhiteOnMove(currentMove)
6744                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6745                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6746             // click in right holdings, for determining promotion piece
6747             ChessSquare p = boards[currentMove][y][x];
6748             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6749             if(p != EmptySquare) {
6750                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6751                 fromX = fromY = -1;
6752                 return;
6753             }
6754         }
6755         DrawPosition(FALSE, boards[currentMove]);
6756         return;
6757     }
6758
6759     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6760     if(clickType == Press
6761             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6762               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6763               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6764         return;
6765
6766     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6767         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6768
6769     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6770         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6771                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6772         defaultPromoChoice = DefaultPromoChoice(side);
6773     }
6774
6775     autoQueen = appData.alwaysPromoteToQueen;
6776
6777     if (fromX == -1) {
6778       int originalY = y;
6779       gatingPiece = EmptySquare;
6780       if (clickType != Press) {
6781         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6782             DragPieceEnd(xPix, yPix); dragging = 0;
6783             DrawPosition(FALSE, NULL);
6784         }
6785         return;
6786       }
6787       fromX = x; fromY = y;
6788       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6789          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6790          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6791             /* First square */
6792             if (OKToStartUserMove(fromX, fromY)) {
6793                 second = 0;
6794                 MarkTargetSquares(0);
6795                 DragPieceBegin(xPix, yPix); dragging = 1;
6796                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6797                     promoSweep = defaultPromoChoice;
6798                     selectFlag = 0; lastX = xPix; lastY = yPix;
6799                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6800                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6801                 }
6802                 if (appData.highlightDragging) {
6803                     SetHighlights(fromX, fromY, -1, -1);
6804                 }
6805             } else fromX = fromY = -1;
6806             return;
6807         }
6808     }
6809
6810     /* fromX != -1 */
6811     if (clickType == Press && gameMode != EditPosition) {
6812         ChessSquare fromP;
6813         ChessSquare toP;
6814         int frc;
6815
6816         // ignore off-board to clicks
6817         if(y < 0 || x < 0) return;
6818
6819         /* Check if clicking again on the same color piece */
6820         fromP = boards[currentMove][fromY][fromX];
6821         toP = boards[currentMove][y][x];
6822         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6823         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6824              WhitePawn <= toP && toP <= WhiteKing &&
6825              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6826              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6827             (BlackPawn <= fromP && fromP <= BlackKing &&
6828              BlackPawn <= toP && toP <= BlackKing &&
6829              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6830              !(fromP == BlackKing && toP == BlackRook && frc))) {
6831             /* Clicked again on same color piece -- changed his mind */
6832             second = (x == fromX && y == fromY);
6833             promoDefaultAltered = FALSE;
6834            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6835             if (appData.highlightDragging) {
6836                 SetHighlights(x, y, -1, -1);
6837             } else {
6838                 ClearHighlights();
6839             }
6840             if (OKToStartUserMove(x, y)) {
6841                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6842                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6843                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6844                  gatingPiece = boards[currentMove][fromY][fromX];
6845                 else gatingPiece = EmptySquare;
6846                 fromX = x;
6847                 fromY = y; dragging = 1;
6848                 MarkTargetSquares(0);
6849                 DragPieceBegin(xPix, yPix);
6850                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6851                     promoSweep = defaultPromoChoice;
6852                     selectFlag = 0; lastX = xPix; lastY = yPix;
6853                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6854                 }
6855             }
6856            }
6857            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6858            second = FALSE; 
6859         }
6860         // ignore clicks on holdings
6861         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6862     }
6863
6864     if (clickType == Release && x == fromX && y == fromY) {
6865         DragPieceEnd(xPix, yPix); dragging = 0;
6866         if(clearFlag) {
6867             // a deferred attempt to click-click move an empty square on top of a piece
6868             boards[currentMove][y][x] = EmptySquare;
6869             ClearHighlights();
6870             DrawPosition(FALSE, boards[currentMove]);
6871             fromX = fromY = -1; clearFlag = 0;
6872             return;
6873         }
6874         if (appData.animateDragging) {
6875             /* Undo animation damage if any */
6876             DrawPosition(FALSE, NULL);
6877         }
6878         if (second) {
6879             /* Second up/down in same square; just abort move */
6880             second = 0;
6881             fromX = fromY = -1;
6882             gatingPiece = EmptySquare;
6883             ClearHighlights();
6884             gotPremove = 0;
6885             ClearPremoveHighlights();
6886         } else {
6887             /* First upclick in same square; start click-click mode */
6888             SetHighlights(x, y, -1, -1);
6889         }
6890         return;
6891     }
6892
6893     clearFlag = 0;
6894
6895     /* we now have a different from- and (possibly off-board) to-square */
6896     /* Completed move */
6897     toX = x;
6898     toY = y;
6899     saveAnimate = appData.animate;
6900     if (clickType == Press) {
6901         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6902             // must be Edit Position mode with empty-square selected
6903             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6904             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6905             return;
6906         }
6907         /* Finish clickclick move */
6908         if (appData.animate || appData.highlightLastMove) {
6909             SetHighlights(fromX, fromY, toX, toY);
6910         } else {
6911             ClearHighlights();
6912         }
6913     } else {
6914         /* Finish drag move */
6915         if (appData.highlightLastMove) {
6916             SetHighlights(fromX, fromY, toX, toY);
6917         } else {
6918             ClearHighlights();
6919         }
6920         DragPieceEnd(xPix, yPix); dragging = 0;
6921         /* Don't animate move and drag both */
6922         appData.animate = FALSE;
6923     }
6924
6925     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6926     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6927         ChessSquare piece = boards[currentMove][fromY][fromX];
6928         if(gameMode == EditPosition && piece != EmptySquare &&
6929            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6930             int n;
6931
6932             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6933                 n = PieceToNumber(piece - (int)BlackPawn);
6934                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6935                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6936                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6937             } else
6938             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6939                 n = PieceToNumber(piece);
6940                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6941                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6942                 boards[currentMove][n][BOARD_WIDTH-2]++;
6943             }
6944             boards[currentMove][fromY][fromX] = EmptySquare;
6945         }
6946         ClearHighlights();
6947         fromX = fromY = -1;
6948         DrawPosition(TRUE, boards[currentMove]);
6949         return;
6950     }
6951
6952     // off-board moves should not be highlighted
6953     if(x < 0 || y < 0) ClearHighlights();
6954
6955     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6956
6957     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6958         SetHighlights(fromX, fromY, toX, toY);
6959         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6960             // [HGM] super: promotion to captured piece selected from holdings
6961             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6962             promotionChoice = TRUE;
6963             // kludge follows to temporarily execute move on display, without promoting yet
6964             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6965             boards[currentMove][toY][toX] = p;
6966             DrawPosition(FALSE, boards[currentMove]);
6967             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6968             boards[currentMove][toY][toX] = q;
6969             DisplayMessage("Click in holdings to choose piece", "");
6970             return;
6971         }
6972         PromotionPopUp();
6973     } else {
6974         int oldMove = currentMove;
6975         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6976         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6977         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6978         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6979            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6980             DrawPosition(TRUE, boards[currentMove]);
6981         fromX = fromY = -1;
6982     }
6983     appData.animate = saveAnimate;
6984     if (appData.animate || appData.animateDragging) {
6985         /* Undo animation damage if needed */
6986         DrawPosition(FALSE, NULL);
6987     }
6988 }
6989
6990 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6991 {   // front-end-free part taken out of PieceMenuPopup
6992     int whichMenu; int xSqr, ySqr;
6993
6994     if(seekGraphUp) { // [HGM] seekgraph
6995         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6996         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6997         return -2;
6998     }
6999
7000     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7001          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7002         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7003         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7004         if(action == Press)   {
7005             originalFlip = flipView;
7006             flipView = !flipView; // temporarily flip board to see game from partners perspective
7007             DrawPosition(TRUE, partnerBoard);
7008             DisplayMessage(partnerStatus, "");
7009             partnerUp = TRUE;
7010         } else if(action == Release) {
7011             flipView = originalFlip;
7012             DrawPosition(TRUE, boards[currentMove]);
7013             partnerUp = FALSE;
7014         }
7015         return -2;
7016     }
7017
7018     xSqr = EventToSquare(x, BOARD_WIDTH);
7019     ySqr = EventToSquare(y, BOARD_HEIGHT);
7020     if (action == Release) {
7021         if(pieceSweep != EmptySquare) {
7022             EditPositionMenuEvent(pieceSweep, toX, toY);
7023             pieceSweep = EmptySquare;
7024         } else UnLoadPV(); // [HGM] pv
7025     }
7026     if (action != Press) return -2; // return code to be ignored
7027     switch (gameMode) {
7028       case IcsExamining:
7029         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7030       case EditPosition:
7031         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7032         if (xSqr < 0 || ySqr < 0) return -1;
7033         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7034         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7035         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7036         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7037         NextPiece(0);
7038         return -2;\r
7039       case IcsObserving:
7040         if(!appData.icsEngineAnalyze) return -1;
7041       case IcsPlayingWhite:
7042       case IcsPlayingBlack:
7043         if(!appData.zippyPlay) goto noZip;
7044       case AnalyzeMode:
7045       case AnalyzeFile:
7046       case MachinePlaysWhite:
7047       case MachinePlaysBlack:
7048       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7049         if (!appData.dropMenu) {
7050           LoadPV(x, y);
7051           return 2; // flag front-end to grab mouse events
7052         }
7053         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7054            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7055       case EditGame:
7056       noZip:
7057         if (xSqr < 0 || ySqr < 0) return -1;
7058         if (!appData.dropMenu || appData.testLegality &&
7059             gameInfo.variant != VariantBughouse &&
7060             gameInfo.variant != VariantCrazyhouse) return -1;
7061         whichMenu = 1; // drop menu
7062         break;
7063       default:
7064         return -1;
7065     }
7066
7067     if (((*fromX = xSqr) < 0) ||
7068         ((*fromY = ySqr) < 0)) {
7069         *fromX = *fromY = -1;
7070         return -1;
7071     }
7072     if (flipView)
7073       *fromX = BOARD_WIDTH - 1 - *fromX;
7074     else
7075       *fromY = BOARD_HEIGHT - 1 - *fromY;
7076
7077     return whichMenu;
7078 }
7079
7080 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7081 {
7082 //    char * hint = lastHint;
7083     FrontEndProgramStats stats;
7084
7085     stats.which = cps == &first ? 0 : 1;
7086     stats.depth = cpstats->depth;
7087     stats.nodes = cpstats->nodes;
7088     stats.score = cpstats->score;
7089     stats.time = cpstats->time;
7090     stats.pv = cpstats->movelist;
7091     stats.hint = lastHint;
7092     stats.an_move_index = 0;
7093     stats.an_move_count = 0;
7094
7095     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7096         stats.hint = cpstats->move_name;
7097         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7098         stats.an_move_count = cpstats->nr_moves;
7099     }
7100
7101     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
7102
7103     SetProgramStats( &stats );
7104 }
7105
7106 #define MAXPLAYERS 500
7107
7108 char *
7109 TourneyStandings(int display)
7110 {
7111     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7112     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7113     char result, *p, *names[MAXPLAYERS];
7114
7115     names[0] = p = strdup(appData.participants);
7116     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7117
7118     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7119
7120     while(result = appData.results[nr]) {
7121         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7122         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7123         wScore = bScore = 0;
7124         switch(result) {
7125           case '+': wScore = 2; break;
7126           case '-': bScore = 2; break;
7127           case '=': wScore = bScore = 1; break;
7128           case ' ':
7129           case '*': return strdup("busy"); // tourney not finished
7130         }
7131         score[w] += wScore;
7132         score[b] += bScore;
7133         games[w]++;
7134         games[b]++;
7135         nr++;
7136     }
7137     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7138     for(w=0; w<nPlayers; w++) {
7139         bScore = -1;
7140         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7141         ranking[w] = b; points[w] = bScore; score[b] = -2;
7142     }
7143     p = malloc(nPlayers*34+1);
7144     for(w=0; w<nPlayers && w<display; w++)
7145         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7146     free(names[0]);
7147     return p;
7148 }
7149
7150 void
7151 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7152 {       // count all piece types
7153         int p, f, r;
7154         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7155         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7156         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7157                 p = board[r][f];
7158                 pCnt[p]++;
7159                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7160                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7161                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7162                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7163                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7164                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7165         }
7166 }
7167
7168 int
7169 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7170 {
7171         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7172         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7173
7174         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7175         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7176         if(myPawns == 2 && nMine == 3) // KPP
7177             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7178         if(myPawns == 1 && nMine == 2) // KP
7179             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7180         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7181             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7182         if(myPawns) return FALSE;
7183         if(pCnt[WhiteRook+side])
7184             return pCnt[BlackRook-side] ||
7185                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7186                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7187                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7188         if(pCnt[WhiteCannon+side]) {
7189             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7190             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7191         }
7192         if(pCnt[WhiteKnight+side])
7193             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7194         return FALSE;
7195 }
7196
7197 int
7198 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7199 {
7200         VariantClass v = gameInfo.variant;
7201
7202         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7203         if(v == VariantShatranj) return TRUE; // always winnable through baring
7204         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7205         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7206
7207         if(v == VariantXiangqi) {
7208                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7209
7210                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7211                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7212                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7213                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7214                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7215                 if(stale) // we have at least one last-rank P plus perhaps C
7216                     return majors // KPKX
7217                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7218                 else // KCA*E*
7219                     return pCnt[WhiteFerz+side] // KCAK
7220                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7221                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7222                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7223
7224         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7225                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7226
7227                 if(nMine == 1) return FALSE; // bare King
7228                 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
7229                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7230                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7231                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7232                 if(pCnt[WhiteKnight+side])
7233                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7234                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7235                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7236                 if(nBishops)
7237                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7238                 if(pCnt[WhiteAlfil+side])
7239                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7240                 if(pCnt[WhiteWazir+side])
7241                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7242         }
7243
7244         return TRUE;
7245 }
7246
7247 int
7248 Adjudicate(ChessProgramState *cps)
7249 {       // [HGM] some adjudications useful with buggy engines
7250         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7251         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7252         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7253         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7254         int k, count = 0; static int bare = 1;
7255         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7256         Boolean canAdjudicate = !appData.icsActive;
7257
7258         // most tests only when we understand the game, i.e. legality-checking on
7259             if( appData.testLegality )
7260             {   /* [HGM] Some more adjudications for obstinate engines */
7261                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7262                 static int moveCount = 6;
7263                 ChessMove result;
7264                 char *reason = NULL;
7265
7266                 /* Count what is on board. */
7267                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7268
7269                 /* Some material-based adjudications that have to be made before stalemate test */
7270                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7271                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7272                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7273                      if(canAdjudicate && appData.checkMates) {
7274                          if(engineOpponent)
7275                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7276                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7277                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7278                          return 1;
7279                      }
7280                 }
7281
7282                 /* Bare King in Shatranj (loses) or Losers (wins) */
7283                 if( nrW == 1 || nrB == 1) {
7284                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7285                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7286                      if(canAdjudicate && appData.checkMates) {
7287                          if(engineOpponent)
7288                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7289                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7290                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7291                          return 1;
7292                      }
7293                   } else
7294                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7295                   {    /* bare King */
7296                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7297                         if(canAdjudicate && appData.checkMates) {
7298                             /* but only adjudicate if adjudication enabled */
7299                             if(engineOpponent)
7300                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7301                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7302                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7303                             return 1;
7304                         }
7305                   }
7306                 } else bare = 1;
7307
7308
7309             // don't wait for engine to announce game end if we can judge ourselves
7310             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7311               case MT_CHECK:
7312                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7313                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7314                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7315                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7316                             checkCnt++;
7317                         if(checkCnt >= 2) {
7318                             reason = "Xboard adjudication: 3rd check";
7319                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7320                             break;
7321                         }
7322                     }
7323                 }
7324               case MT_NONE:
7325               default:
7326                 break;
7327               case MT_STALEMATE:
7328               case MT_STAINMATE:
7329                 reason = "Xboard adjudication: Stalemate";
7330                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7331                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7332                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7333                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7334                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7335                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7336                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7337                                                                         EP_CHECKMATE : EP_WINS);
7338                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7339                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7340                 }
7341                 break;
7342               case MT_CHECKMATE:
7343                 reason = "Xboard adjudication: Checkmate";
7344                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7345                 break;
7346             }
7347
7348                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7349                     case EP_STALEMATE:
7350                         result = GameIsDrawn; break;
7351                     case EP_CHECKMATE:
7352                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7353                     case EP_WINS:
7354                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7355                     default:
7356                         result = EndOfFile;
7357                 }
7358                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7359                     if(engineOpponent)
7360                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7361                     GameEnds( result, reason, GE_XBOARD );
7362                     return 1;
7363                 }
7364
7365                 /* Next absolutely insufficient mating material. */
7366                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7367                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7368                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7369
7370                      /* always flag draws, for judging claims */
7371                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7372
7373                      if(canAdjudicate && appData.materialDraws) {
7374                          /* but only adjudicate them if adjudication enabled */
7375                          if(engineOpponent) {
7376                            SendToProgram("force\n", engineOpponent); // suppress reply
7377                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7378                          }
7379                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7380                          return 1;
7381                      }
7382                 }
7383
7384                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7385                 if(gameInfo.variant == VariantXiangqi ?
7386                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7387                  : nrW + nrB == 4 &&
7388                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7389                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7390                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7391                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7392                    ) ) {
7393                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7394                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7395                           if(engineOpponent) {
7396                             SendToProgram("force\n", engineOpponent); // suppress reply
7397                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7398                           }
7399                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7400                           return 1;
7401                      }
7402                 } else moveCount = 6;
7403             }
7404         if (appData.debugMode) { int i;
7405             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7406                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7407                     appData.drawRepeats);
7408             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7409               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7410
7411         }
7412
7413         // Repetition draws and 50-move rule can be applied independently of legality testing
7414
7415                 /* Check for rep-draws */
7416                 count = 0;
7417                 for(k = forwardMostMove-2;
7418                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7419                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7420                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7421                     k-=2)
7422                 {   int rights=0;
7423                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7424                         /* compare castling rights */
7425                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7426                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7427                                 rights++; /* King lost rights, while rook still had them */
7428                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7429                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7430                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7431                                    rights++; /* but at least one rook lost them */
7432                         }
7433                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7434                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7435                                 rights++;
7436                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7437                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7438                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7439                                    rights++;
7440                         }
7441                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7442                             && appData.drawRepeats > 1) {
7443                              /* adjudicate after user-specified nr of repeats */
7444                              int result = GameIsDrawn;
7445                              char *details = "XBoard adjudication: repetition draw";
7446                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7447                                 // [HGM] xiangqi: check for forbidden perpetuals
7448                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7449                                 for(m=forwardMostMove; m>k; m-=2) {
7450                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7451                                         ourPerpetual = 0; // the current mover did not always check
7452                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7453                                         hisPerpetual = 0; // the opponent did not always check
7454                                 }
7455                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7456                                                                         ourPerpetual, hisPerpetual);
7457                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7458                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7459                                     details = "Xboard adjudication: perpetual checking";
7460                                 } else
7461                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7462                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7463                                 } else
7464                                 // Now check for perpetual chases
7465                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7466                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7467                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7468                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7469                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7470                                         details = "Xboard adjudication: perpetual chasing";
7471                                     } else
7472                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7473                                         break; // Abort repetition-checking loop.
7474                                 }
7475                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7476                              }
7477                              if(engineOpponent) {
7478                                SendToProgram("force\n", engineOpponent); // suppress reply
7479                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7480                              }
7481                              GameEnds( result, details, GE_XBOARD );
7482                              return 1;
7483                         }
7484                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7485                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7486                     }
7487                 }
7488
7489                 /* Now we test for 50-move draws. Determine ply count */
7490                 count = forwardMostMove;
7491                 /* look for last irreversble move */
7492                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7493                     count--;
7494                 /* if we hit starting position, add initial plies */
7495                 if( count == backwardMostMove )
7496                     count -= initialRulePlies;
7497                 count = forwardMostMove - count;
7498                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7499                         // adjust reversible move counter for checks in Xiangqi
7500                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7501                         if(i < backwardMostMove) i = backwardMostMove;
7502                         while(i <= forwardMostMove) {
7503                                 lastCheck = inCheck; // check evasion does not count
7504                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7505                                 if(inCheck || lastCheck) count--; // check does not count
7506                                 i++;
7507                         }
7508                 }
7509                 if( count >= 100)
7510                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7511                          /* this is used to judge if draw claims are legal */
7512                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7513                          if(engineOpponent) {
7514                            SendToProgram("force\n", engineOpponent); // suppress reply
7515                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7516                          }
7517                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7518                          return 1;
7519                 }
7520
7521                 /* if draw offer is pending, treat it as a draw claim
7522                  * when draw condition present, to allow engines a way to
7523                  * claim draws before making their move to avoid a race
7524                  * condition occurring after their move
7525                  */
7526                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7527                          char *p = NULL;
7528                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7529                              p = "Draw claim: 50-move rule";
7530                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7531                              p = "Draw claim: 3-fold repetition";
7532                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7533                              p = "Draw claim: insufficient mating material";
7534                          if( p != NULL && canAdjudicate) {
7535                              if(engineOpponent) {
7536                                SendToProgram("force\n", engineOpponent); // suppress reply
7537                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7538                              }
7539                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7540                              return 1;
7541                          }
7542                 }
7543
7544                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7545                     if(engineOpponent) {
7546                       SendToProgram("force\n", engineOpponent); // suppress reply
7547                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7548                     }
7549                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7550                     return 1;
7551                 }
7552         return 0;
7553 }
7554
7555 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7556 {   // [HGM] book: this routine intercepts moves to simulate book replies
7557     char *bookHit = NULL;
7558
7559     //first determine if the incoming move brings opponent into his book
7560     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7561         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7562     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7563     if(bookHit != NULL && !cps->bookSuspend) {
7564         // make sure opponent is not going to reply after receiving move to book position
7565         SendToProgram("force\n", cps);
7566         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7567     }
7568     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7569     // now arrange restart after book miss
7570     if(bookHit) {
7571         // after a book hit we never send 'go', and the code after the call to this routine
7572         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7573         char buf[MSG_SIZ];
7574         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7575         SendToProgram(buf, cps);
7576         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7577     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7578         SendToProgram("go\n", cps);
7579         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7580     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7581         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7582             SendToProgram("go\n", cps);
7583         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7584     }
7585     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7586 }
7587
7588 char *savedMessage;
7589 ChessProgramState *savedState;
7590 void DeferredBookMove(void)
7591 {
7592         if(savedState->lastPing != savedState->lastPong)
7593                     ScheduleDelayedEvent(DeferredBookMove, 10);
7594         else
7595         HandleMachineMove(savedMessage, savedState);
7596 }
7597
7598 void
7599 HandleMachineMove(message, cps)
7600      char *message;
7601      ChessProgramState *cps;
7602 {
7603     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7604     char realname[MSG_SIZ];
7605     int fromX, fromY, toX, toY;
7606     ChessMove moveType;
7607     char promoChar;
7608     char *p;
7609     int machineWhite;
7610     char *bookHit;
7611
7612     cps->userError = 0;
7613
7614 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7615     /*
7616      * Kludge to ignore BEL characters
7617      */
7618     while (*message == '\007') message++;
7619
7620     /*
7621      * [HGM] engine debug message: ignore lines starting with '#' character
7622      */
7623     if(cps->debug && *message == '#') return;
7624
7625     /*
7626      * Look for book output
7627      */
7628     if (cps == &first && bookRequested) {
7629         if (message[0] == '\t' || message[0] == ' ') {
7630             /* Part of the book output is here; append it */
7631             strcat(bookOutput, message);
7632             strcat(bookOutput, "  \n");
7633             return;
7634         } else if (bookOutput[0] != NULLCHAR) {
7635             /* All of book output has arrived; display it */
7636             char *p = bookOutput;
7637             while (*p != NULLCHAR) {
7638                 if (*p == '\t') *p = ' ';
7639                 p++;
7640             }
7641             DisplayInformation(bookOutput);
7642             bookRequested = FALSE;
7643             /* Fall through to parse the current output */
7644         }
7645     }
7646
7647     /*
7648      * Look for machine move.
7649      */
7650     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7651         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7652     {
7653         /* This method is only useful on engines that support ping */
7654         if (cps->lastPing != cps->lastPong) {
7655           if (gameMode == BeginningOfGame) {
7656             /* Extra move from before last new; ignore */
7657             if (appData.debugMode) {
7658                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7659             }
7660           } else {
7661             if (appData.debugMode) {
7662                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7663                         cps->which, gameMode);
7664             }
7665
7666             SendToProgram("undo\n", cps);
7667           }
7668           return;
7669         }
7670
7671         switch (gameMode) {
7672           case BeginningOfGame:
7673             /* Extra move from before last reset; ignore */
7674             if (appData.debugMode) {
7675                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7676             }
7677             return;
7678
7679           case EndOfGame:
7680           case IcsIdle:
7681           default:
7682             /* Extra move after we tried to stop.  The mode test is
7683                not a reliable way of detecting this problem, but it's
7684                the best we can do on engines that don't support ping.
7685             */
7686             if (appData.debugMode) {
7687                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7688                         cps->which, gameMode);
7689             }
7690             SendToProgram("undo\n", cps);
7691             return;
7692
7693           case MachinePlaysWhite:
7694           case IcsPlayingWhite:
7695             machineWhite = TRUE;
7696             break;
7697
7698           case MachinePlaysBlack:
7699           case IcsPlayingBlack:
7700             machineWhite = FALSE;
7701             break;
7702
7703           case TwoMachinesPlay:
7704             machineWhite = (cps->twoMachinesColor[0] == 'w');
7705             break;
7706         }
7707         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7708             if (appData.debugMode) {
7709                 fprintf(debugFP,
7710                         "Ignoring move out of turn by %s, gameMode %d"
7711                         ", forwardMost %d\n",
7712                         cps->which, gameMode, forwardMostMove);
7713             }
7714             return;
7715         }
7716
7717     if (appData.debugMode) { int f = forwardMostMove;
7718         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7719                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7720                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7721     }
7722         if(cps->alphaRank) AlphaRank(machineMove, 4);
7723         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7724                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7725             /* Machine move could not be parsed; ignore it. */
7726           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7727                     machineMove, _(cps->which));
7728             DisplayError(buf1, 0);
7729             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7730                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7731             if (gameMode == TwoMachinesPlay) {
7732               GameEnds(machineWhite ? BlackWins : WhiteWins,
7733                        buf1, GE_XBOARD);
7734             }
7735             return;
7736         }
7737
7738         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7739         /* So we have to redo legality test with true e.p. status here,  */
7740         /* to make sure an illegal e.p. capture does not slip through,   */
7741         /* to cause a forfeit on a justified illegal-move complaint      */
7742         /* of the opponent.                                              */
7743         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7744            ChessMove moveType;
7745            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7746                              fromY, fromX, toY, toX, promoChar);
7747             if (appData.debugMode) {
7748                 int i;
7749                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7750                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7751                 fprintf(debugFP, "castling rights\n");
7752             }
7753             if(moveType == IllegalMove) {
7754               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7755                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7756                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7757                            buf1, GE_XBOARD);
7758                 return;
7759            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7760            /* [HGM] Kludge to handle engines that send FRC-style castling
7761               when they shouldn't (like TSCP-Gothic) */
7762            switch(moveType) {
7763              case WhiteASideCastleFR:
7764              case BlackASideCastleFR:
7765                toX+=2;
7766                currentMoveString[2]++;
7767                break;
7768              case WhiteHSideCastleFR:
7769              case BlackHSideCastleFR:
7770                toX--;
7771                currentMoveString[2]--;
7772                break;
7773              default: ; // nothing to do, but suppresses warning of pedantic compilers
7774            }
7775         }
7776         hintRequested = FALSE;
7777         lastHint[0] = NULLCHAR;
7778         bookRequested = FALSE;
7779         /* Program may be pondering now */
7780         cps->maybeThinking = TRUE;
7781         if (cps->sendTime == 2) cps->sendTime = 1;
7782         if (cps->offeredDraw) cps->offeredDraw--;
7783
7784         /* [AS] Save move info*/
7785         pvInfoList[ forwardMostMove ].score = programStats.score;
7786         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7787         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7788
7789         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7790
7791         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7792         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7793             int count = 0;
7794
7795             while( count < adjudicateLossPlies ) {
7796                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7797
7798                 if( count & 1 ) {
7799                     score = -score; /* Flip score for winning side */
7800                 }
7801
7802                 if( score > adjudicateLossThreshold ) {
7803                     break;
7804                 }
7805
7806                 count++;
7807             }
7808
7809             if( count >= adjudicateLossPlies ) {
7810                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7811
7812                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7813                     "Xboard adjudication",
7814                     GE_XBOARD );
7815
7816                 return;
7817             }
7818         }
7819
7820         if(Adjudicate(cps)) {
7821             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7822             return; // [HGM] adjudicate: for all automatic game ends
7823         }
7824
7825 #if ZIPPY
7826         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7827             first.initDone) {
7828           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7829                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7830                 SendToICS("draw ");
7831                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7832           }
7833           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7834           ics_user_moved = 1;
7835           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7836                 char buf[3*MSG_SIZ];
7837
7838                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7839                         programStats.score / 100.,
7840                         programStats.depth,
7841                         programStats.time / 100.,
7842                         (unsigned int)programStats.nodes,
7843                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7844                         programStats.movelist);
7845                 SendToICS(buf);
7846 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7847           }
7848         }
7849 #endif
7850
7851         /* [AS] Clear stats for next move */
7852         ClearProgramStats();
7853         thinkOutput[0] = NULLCHAR;
7854         hiddenThinkOutputState = 0;
7855
7856         bookHit = NULL;
7857         if (gameMode == TwoMachinesPlay) {
7858             /* [HGM] relaying draw offers moved to after reception of move */
7859             /* and interpreting offer as claim if it brings draw condition */
7860             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7861                 SendToProgram("draw\n", cps->other);
7862             }
7863             if (cps->other->sendTime) {
7864                 SendTimeRemaining(cps->other,
7865                                   cps->other->twoMachinesColor[0] == 'w');
7866             }
7867             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7868             if (firstMove && !bookHit) {
7869                 firstMove = FALSE;
7870                 if (cps->other->useColors) {
7871                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7872                 }
7873                 SendToProgram("go\n", cps->other);
7874             }
7875             cps->other->maybeThinking = TRUE;
7876         }
7877
7878         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7879
7880         if (!pausing && appData.ringBellAfterMoves) {
7881             RingBell();
7882         }
7883
7884         /*
7885          * Reenable menu items that were disabled while
7886          * machine was thinking
7887          */
7888         if (gameMode != TwoMachinesPlay)
7889             SetUserThinkingEnables();
7890
7891         // [HGM] book: after book hit opponent has received move and is now in force mode
7892         // force the book reply into it, and then fake that it outputted this move by jumping
7893         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7894         if(bookHit) {
7895                 static char bookMove[MSG_SIZ]; // a bit generous?
7896
7897                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7898                 strcat(bookMove, bookHit);
7899                 message = bookMove;
7900                 cps = cps->other;
7901                 programStats.nodes = programStats.depth = programStats.time =
7902                 programStats.score = programStats.got_only_move = 0;
7903                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7904
7905                 if(cps->lastPing != cps->lastPong) {
7906                     savedMessage = message; // args for deferred call
7907                     savedState = cps;
7908                     ScheduleDelayedEvent(DeferredBookMove, 10);
7909                     return;
7910                 }
7911                 goto FakeBookMove;
7912         }
7913
7914         return;
7915     }
7916
7917     /* Set special modes for chess engines.  Later something general
7918      *  could be added here; for now there is just one kludge feature,
7919      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7920      *  when "xboard" is given as an interactive command.
7921      */
7922     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7923         cps->useSigint = FALSE;
7924         cps->useSigterm = FALSE;
7925     }
7926     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7927       ParseFeatures(message+8, cps);
7928       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7929     }
7930
7931     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7932       int dummy, s=6; char buf[MSG_SIZ];
7933       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7934       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7935       ParseFEN(boards[0], &dummy, message+s);
7936       DrawPosition(TRUE, boards[0]);
7937       startedFromSetupPosition = TRUE;
7938       return;
7939     }
7940     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7941      * want this, I was asked to put it in, and obliged.
7942      */
7943     if (!strncmp(message, "setboard ", 9)) {
7944         Board initial_position;
7945
7946         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7947
7948         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7949             DisplayError(_("Bad FEN received from engine"), 0);
7950             return ;
7951         } else {
7952            Reset(TRUE, FALSE);
7953            CopyBoard(boards[0], initial_position);
7954            initialRulePlies = FENrulePlies;
7955            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7956            else gameMode = MachinePlaysBlack;
7957            DrawPosition(FALSE, boards[currentMove]);
7958         }
7959         return;
7960     }
7961
7962     /*
7963      * Look for communication commands
7964      */
7965     if (!strncmp(message, "telluser ", 9)) {
7966         if(message[9] == '\\' && message[10] == '\\')
7967             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7968         DisplayNote(message + 9);
7969         return;
7970     }
7971     if (!strncmp(message, "tellusererror ", 14)) {
7972         cps->userError = 1;
7973         if(message[14] == '\\' && message[15] == '\\')
7974             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7975         DisplayError(message + 14, 0);
7976         return;
7977     }
7978     if (!strncmp(message, "tellopponent ", 13)) {
7979       if (appData.icsActive) {
7980         if (loggedOn) {
7981           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7982           SendToICS(buf1);
7983         }
7984       } else {
7985         DisplayNote(message + 13);
7986       }
7987       return;
7988     }
7989     if (!strncmp(message, "tellothers ", 11)) {
7990       if (appData.icsActive) {
7991         if (loggedOn) {
7992           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7993           SendToICS(buf1);
7994         }
7995       }
7996       return;
7997     }
7998     if (!strncmp(message, "tellall ", 8)) {
7999       if (appData.icsActive) {
8000         if (loggedOn) {
8001           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8002           SendToICS(buf1);
8003         }
8004       } else {
8005         DisplayNote(message + 8);
8006       }
8007       return;
8008     }
8009     if (strncmp(message, "warning", 7) == 0) {
8010         /* Undocumented feature, use tellusererror in new code */
8011         DisplayError(message, 0);
8012         return;
8013     }
8014     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8015         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8016         strcat(realname, " query");
8017         AskQuestion(realname, buf2, buf1, cps->pr);
8018         return;
8019     }
8020     /* Commands from the engine directly to ICS.  We don't allow these to be
8021      *  sent until we are logged on. Crafty kibitzes have been known to
8022      *  interfere with the login process.
8023      */
8024     if (loggedOn) {
8025         if (!strncmp(message, "tellics ", 8)) {
8026             SendToICS(message + 8);
8027             SendToICS("\n");
8028             return;
8029         }
8030         if (!strncmp(message, "tellicsnoalias ", 15)) {
8031             SendToICS(ics_prefix);
8032             SendToICS(message + 15);
8033             SendToICS("\n");
8034             return;
8035         }
8036         /* The following are for backward compatibility only */
8037         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8038             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8039             SendToICS(ics_prefix);
8040             SendToICS(message);
8041             SendToICS("\n");
8042             return;
8043         }
8044     }
8045     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8046         return;
8047     }
8048     /*
8049      * If the move is illegal, cancel it and redraw the board.
8050      * Also deal with other error cases.  Matching is rather loose
8051      * here to accommodate engines written before the spec.
8052      */
8053     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8054         strncmp(message, "Error", 5) == 0) {
8055         if (StrStr(message, "name") ||
8056             StrStr(message, "rating") || StrStr(message, "?") ||
8057             StrStr(message, "result") || StrStr(message, "board") ||
8058             StrStr(message, "bk") || StrStr(message, "computer") ||
8059             StrStr(message, "variant") || StrStr(message, "hint") ||
8060             StrStr(message, "random") || StrStr(message, "depth") ||
8061             StrStr(message, "accepted")) {
8062             return;
8063         }
8064         if (StrStr(message, "protover")) {
8065           /* Program is responding to input, so it's apparently done
8066              initializing, and this error message indicates it is
8067              protocol version 1.  So we don't need to wait any longer
8068              for it to initialize and send feature commands. */
8069           FeatureDone(cps, 1);
8070           cps->protocolVersion = 1;
8071           return;
8072         }
8073         cps->maybeThinking = FALSE;
8074
8075         if (StrStr(message, "draw")) {
8076             /* Program doesn't have "draw" command */
8077             cps->sendDrawOffers = 0;
8078             return;
8079         }
8080         if (cps->sendTime != 1 &&
8081             (StrStr(message, "time") || StrStr(message, "otim"))) {
8082           /* Program apparently doesn't have "time" or "otim" command */
8083           cps->sendTime = 0;
8084           return;
8085         }
8086         if (StrStr(message, "analyze")) {
8087             cps->analysisSupport = FALSE;
8088             cps->analyzing = FALSE;
8089             Reset(FALSE, TRUE);
8090             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8091             DisplayError(buf2, 0);
8092             return;
8093         }
8094         if (StrStr(message, "(no matching move)st")) {
8095           /* Special kludge for GNU Chess 4 only */
8096           cps->stKludge = TRUE;
8097           SendTimeControl(cps, movesPerSession, timeControl,
8098                           timeIncrement, appData.searchDepth,
8099                           searchTime);
8100           return;
8101         }
8102         if (StrStr(message, "(no matching move)sd")) {
8103           /* Special kludge for GNU Chess 4 only */
8104           cps->sdKludge = TRUE;
8105           SendTimeControl(cps, movesPerSession, timeControl,
8106                           timeIncrement, appData.searchDepth,
8107                           searchTime);
8108           return;
8109         }
8110         if (!StrStr(message, "llegal")) {
8111             return;
8112         }
8113         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8114             gameMode == IcsIdle) return;
8115         if (forwardMostMove <= backwardMostMove) return;
8116         if (pausing) PauseEvent();
8117       if(appData.forceIllegal) {
8118             // [HGM] illegal: machine refused move; force position after move into it
8119           SendToProgram("force\n", cps);
8120           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8121                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8122                 // when black is to move, while there might be nothing on a2 or black
8123                 // might already have the move. So send the board as if white has the move.
8124                 // But first we must change the stm of the engine, as it refused the last move
8125                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8126                 if(WhiteOnMove(forwardMostMove)) {
8127                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8128                     SendBoard(cps, forwardMostMove); // kludgeless board
8129                 } else {
8130                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8131                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8132                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8133                 }
8134           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8135             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8136                  gameMode == TwoMachinesPlay)
8137               SendToProgram("go\n", cps);
8138             return;
8139       } else
8140         if (gameMode == PlayFromGameFile) {
8141             /* Stop reading this game file */
8142             gameMode = EditGame;
8143             ModeHighlight();
8144         }
8145         /* [HGM] illegal-move claim should forfeit game when Xboard */
8146         /* only passes fully legal moves                            */
8147         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8148             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8149                                 "False illegal-move claim", GE_XBOARD );
8150             return; // do not take back move we tested as valid
8151         }
8152         currentMove = forwardMostMove-1;
8153         DisplayMove(currentMove-1); /* before DisplayMoveError */
8154         SwitchClocks(forwardMostMove-1); // [HGM] race
8155         DisplayBothClocks();
8156         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8157                 parseList[currentMove], _(cps->which));
8158         DisplayMoveError(buf1);
8159         DrawPosition(FALSE, boards[currentMove]);
8160         return;
8161     }
8162     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8163         /* Program has a broken "time" command that
8164            outputs a string not ending in newline.
8165            Don't use it. */
8166         cps->sendTime = 0;
8167     }
8168
8169     /*
8170      * If chess program startup fails, exit with an error message.
8171      * Attempts to recover here are futile.
8172      */
8173     if ((StrStr(message, "unknown host") != NULL)
8174         || (StrStr(message, "No remote directory") != NULL)
8175         || (StrStr(message, "not found") != NULL)
8176         || (StrStr(message, "No such file") != NULL)
8177         || (StrStr(message, "can't alloc") != NULL)
8178         || (StrStr(message, "Permission denied") != NULL)) {
8179
8180         cps->maybeThinking = FALSE;
8181         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8182                 _(cps->which), cps->program, cps->host, message);
8183         RemoveInputSource(cps->isr);
8184         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8185             if(cps == &first) appData.noChessProgram = TRUE;
8186             DisplayError(buf1, 0);
8187         }
8188         return;
8189     }
8190
8191     /*
8192      * Look for hint output
8193      */
8194     if (sscanf(message, "Hint: %s", buf1) == 1) {
8195         if (cps == &first && hintRequested) {
8196             hintRequested = FALSE;
8197             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8198                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8199                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8200                                     PosFlags(forwardMostMove),
8201                                     fromY, fromX, toY, toX, promoChar, buf1);
8202                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8203                 DisplayInformation(buf2);
8204             } else {
8205                 /* Hint move could not be parsed!? */
8206               snprintf(buf2, sizeof(buf2),
8207                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8208                         buf1, _(cps->which));
8209                 DisplayError(buf2, 0);
8210             }
8211         } else {
8212           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8213         }
8214         return;
8215     }
8216
8217     /*
8218      * Ignore other messages if game is not in progress
8219      */
8220     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8221         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8222
8223     /*
8224      * look for win, lose, draw, or draw offer
8225      */
8226     if (strncmp(message, "1-0", 3) == 0) {
8227         char *p, *q, *r = "";
8228         p = strchr(message, '{');
8229         if (p) {
8230             q = strchr(p, '}');
8231             if (q) {
8232                 *q = NULLCHAR;
8233                 r = p + 1;
8234             }
8235         }
8236         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8237         return;
8238     } else if (strncmp(message, "0-1", 3) == 0) {
8239         char *p, *q, *r = "";
8240         p = strchr(message, '{');
8241         if (p) {
8242             q = strchr(p, '}');
8243             if (q) {
8244                 *q = NULLCHAR;
8245                 r = p + 1;
8246             }
8247         }
8248         /* Kludge for Arasan 4.1 bug */
8249         if (strcmp(r, "Black resigns") == 0) {
8250             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8251             return;
8252         }
8253         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8254         return;
8255     } else if (strncmp(message, "1/2", 3) == 0) {
8256         char *p, *q, *r = "";
8257         p = strchr(message, '{');
8258         if (p) {
8259             q = strchr(p, '}');
8260             if (q) {
8261                 *q = NULLCHAR;
8262                 r = p + 1;
8263             }
8264         }
8265
8266         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8267         return;
8268
8269     } else if (strncmp(message, "White resign", 12) == 0) {
8270         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8271         return;
8272     } else if (strncmp(message, "Black resign", 12) == 0) {
8273         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8274         return;
8275     } else if (strncmp(message, "White matches", 13) == 0 ||
8276                strncmp(message, "Black matches", 13) == 0   ) {
8277         /* [HGM] ignore GNUShogi noises */
8278         return;
8279     } else if (strncmp(message, "White", 5) == 0 &&
8280                message[5] != '(' &&
8281                StrStr(message, "Black") == NULL) {
8282         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8283         return;
8284     } else if (strncmp(message, "Black", 5) == 0 &&
8285                message[5] != '(') {
8286         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8287         return;
8288     } else if (strcmp(message, "resign") == 0 ||
8289                strcmp(message, "computer resigns") == 0) {
8290         switch (gameMode) {
8291           case MachinePlaysBlack:
8292           case IcsPlayingBlack:
8293             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8294             break;
8295           case MachinePlaysWhite:
8296           case IcsPlayingWhite:
8297             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8298             break;
8299           case TwoMachinesPlay:
8300             if (cps->twoMachinesColor[0] == 'w')
8301               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8302             else
8303               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8304             break;
8305           default:
8306             /* can't happen */
8307             break;
8308         }
8309         return;
8310     } else if (strncmp(message, "opponent mates", 14) == 0) {
8311         switch (gameMode) {
8312           case MachinePlaysBlack:
8313           case IcsPlayingBlack:
8314             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8315             break;
8316           case MachinePlaysWhite:
8317           case IcsPlayingWhite:
8318             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8319             break;
8320           case TwoMachinesPlay:
8321             if (cps->twoMachinesColor[0] == 'w')
8322               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8323             else
8324               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8325             break;
8326           default:
8327             /* can't happen */
8328             break;
8329         }
8330         return;
8331     } else if (strncmp(message, "computer mates", 14) == 0) {
8332         switch (gameMode) {
8333           case MachinePlaysBlack:
8334           case IcsPlayingBlack:
8335             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8336             break;
8337           case MachinePlaysWhite:
8338           case IcsPlayingWhite:
8339             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8340             break;
8341           case TwoMachinesPlay:
8342             if (cps->twoMachinesColor[0] == 'w')
8343               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8344             else
8345               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8346             break;
8347           default:
8348             /* can't happen */
8349             break;
8350         }
8351         return;
8352     } else if (strncmp(message, "checkmate", 9) == 0) {
8353         if (WhiteOnMove(forwardMostMove)) {
8354             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8355         } else {
8356             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8357         }
8358         return;
8359     } else if (strstr(message, "Draw") != NULL ||
8360                strstr(message, "game is a draw") != NULL) {
8361         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8362         return;
8363     } else if (strstr(message, "offer") != NULL &&
8364                strstr(message, "draw") != NULL) {
8365 #if ZIPPY
8366         if (appData.zippyPlay && first.initDone) {
8367             /* Relay offer to ICS */
8368             SendToICS(ics_prefix);
8369             SendToICS("draw\n");
8370         }
8371 #endif
8372         cps->offeredDraw = 2; /* valid until this engine moves twice */
8373         if (gameMode == TwoMachinesPlay) {
8374             if (cps->other->offeredDraw) {
8375                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8376             /* [HGM] in two-machine mode we delay relaying draw offer      */
8377             /* until after we also have move, to see if it is really claim */
8378             }
8379         } else if (gameMode == MachinePlaysWhite ||
8380                    gameMode == MachinePlaysBlack) {
8381           if (userOfferedDraw) {
8382             DisplayInformation(_("Machine accepts your draw offer"));
8383             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8384           } else {
8385             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8386           }
8387         }
8388     }
8389
8390
8391     /*
8392      * Look for thinking output
8393      */
8394     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8395           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8396                                 ) {
8397         int plylev, mvleft, mvtot, curscore, time;
8398         char mvname[MOVE_LEN];
8399         u64 nodes; // [DM]
8400         char plyext;
8401         int ignore = FALSE;
8402         int prefixHint = FALSE;
8403         mvname[0] = NULLCHAR;
8404
8405         switch (gameMode) {
8406           case MachinePlaysBlack:
8407           case IcsPlayingBlack:
8408             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8409             break;
8410           case MachinePlaysWhite:
8411           case IcsPlayingWhite:
8412             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8413             break;
8414           case AnalyzeMode:
8415           case AnalyzeFile:
8416             break;
8417           case IcsObserving: /* [DM] icsEngineAnalyze */
8418             if (!appData.icsEngineAnalyze) ignore = TRUE;
8419             break;
8420           case TwoMachinesPlay:
8421             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8422                 ignore = TRUE;
8423             }
8424             break;
8425           default:
8426             ignore = TRUE;
8427             break;
8428         }
8429
8430         if (!ignore) {
8431             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8432             buf1[0] = NULLCHAR;
8433             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8434                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8435
8436                 if (plyext != ' ' && plyext != '\t') {
8437                     time *= 100;
8438                 }
8439
8440                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8441                 if( cps->scoreIsAbsolute &&
8442                     ( gameMode == MachinePlaysBlack ||
8443                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8444                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8445                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8446                      !WhiteOnMove(currentMove)
8447                     ) )
8448                 {
8449                     curscore = -curscore;
8450                 }
8451
8452
8453                 tempStats.depth = plylev;
8454                 tempStats.nodes = nodes;
8455                 tempStats.time = time;
8456                 tempStats.score = curscore;
8457                 tempStats.got_only_move = 0;
8458
8459                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8460                         int ticklen;
8461
8462                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8463                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8464                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8465                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8466                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8467                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8468                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8469                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8470                 }
8471
8472                 /* Buffer overflow protection */
8473                 if (buf1[0] != NULLCHAR) {
8474                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8475                         && appData.debugMode) {
8476                         fprintf(debugFP,
8477                                 "PV is too long; using the first %u bytes.\n",
8478                                 (unsigned) sizeof(tempStats.movelist) - 1);
8479                     }
8480
8481                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8482                 } else {
8483                     sprintf(tempStats.movelist, " no PV\n");
8484                 }
8485
8486                 if (tempStats.seen_stat) {
8487                     tempStats.ok_to_send = 1;
8488                 }
8489
8490                 if (strchr(tempStats.movelist, '(') != NULL) {
8491                     tempStats.line_is_book = 1;
8492                     tempStats.nr_moves = 0;
8493                     tempStats.moves_left = 0;
8494                 } else {
8495                     tempStats.line_is_book = 0;
8496                 }
8497
8498                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8499                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8500
8501                 SendProgramStatsToFrontend( cps, &tempStats );
8502
8503                 /*
8504                     [AS] Protect the thinkOutput buffer from overflow... this
8505                     is only useful if buf1 hasn't overflowed first!
8506                 */
8507                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8508                          plylev,
8509                          (gameMode == TwoMachinesPlay ?
8510                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8511                          ((double) curscore) / 100.0,
8512                          prefixHint ? lastHint : "",
8513                          prefixHint ? " " : "" );
8514
8515                 if( buf1[0] != NULLCHAR ) {
8516                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8517
8518                     if( strlen(buf1) > max_len ) {
8519                         if( appData.debugMode) {
8520                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8521                         }
8522                         buf1[max_len+1] = '\0';
8523                     }
8524
8525                     strcat( thinkOutput, buf1 );
8526                 }
8527
8528                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8529                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8530                     DisplayMove(currentMove - 1);
8531                 }
8532                 return;
8533
8534             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8535                 /* crafty (9.25+) says "(only move) <move>"
8536                  * if there is only 1 legal move
8537                  */
8538                 sscanf(p, "(only move) %s", buf1);
8539                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8540                 sprintf(programStats.movelist, "%s (only move)", buf1);
8541                 programStats.depth = 1;
8542                 programStats.nr_moves = 1;
8543                 programStats.moves_left = 1;
8544                 programStats.nodes = 1;
8545                 programStats.time = 1;
8546                 programStats.got_only_move = 1;
8547
8548                 /* Not really, but we also use this member to
8549                    mean "line isn't going to change" (Crafty
8550                    isn't searching, so stats won't change) */
8551                 programStats.line_is_book = 1;
8552
8553                 SendProgramStatsToFrontend( cps, &programStats );
8554
8555                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8556                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8557                     DisplayMove(currentMove - 1);
8558                 }
8559                 return;
8560             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8561                               &time, &nodes, &plylev, &mvleft,
8562                               &mvtot, mvname) >= 5) {
8563                 /* The stat01: line is from Crafty (9.29+) in response
8564                    to the "." command */
8565                 programStats.seen_stat = 1;
8566                 cps->maybeThinking = TRUE;
8567
8568                 if (programStats.got_only_move || !appData.periodicUpdates)
8569                   return;
8570
8571                 programStats.depth = plylev;
8572                 programStats.time = time;
8573                 programStats.nodes = nodes;
8574                 programStats.moves_left = mvleft;
8575                 programStats.nr_moves = mvtot;
8576                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8577                 programStats.ok_to_send = 1;
8578                 programStats.movelist[0] = '\0';
8579
8580                 SendProgramStatsToFrontend( cps, &programStats );
8581
8582                 return;
8583
8584             } else if (strncmp(message,"++",2) == 0) {
8585                 /* Crafty 9.29+ outputs this */
8586                 programStats.got_fail = 2;
8587                 return;
8588
8589             } else if (strncmp(message,"--",2) == 0) {
8590                 /* Crafty 9.29+ outputs this */
8591                 programStats.got_fail = 1;
8592                 return;
8593
8594             } else if (thinkOutput[0] != NULLCHAR &&
8595                        strncmp(message, "    ", 4) == 0) {
8596                 unsigned message_len;
8597
8598                 p = message;
8599                 while (*p && *p == ' ') p++;
8600
8601                 message_len = strlen( p );
8602
8603                 /* [AS] Avoid buffer overflow */
8604                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8605                     strcat(thinkOutput, " ");
8606                     strcat(thinkOutput, p);
8607                 }
8608
8609                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8610                     strcat(programStats.movelist, " ");
8611                     strcat(programStats.movelist, p);
8612                 }
8613
8614                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8615                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8616                     DisplayMove(currentMove - 1);
8617                 }
8618                 return;
8619             }
8620         }
8621         else {
8622             buf1[0] = NULLCHAR;
8623
8624             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8625                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8626             {
8627                 ChessProgramStats cpstats;
8628
8629                 if (plyext != ' ' && plyext != '\t') {
8630                     time *= 100;
8631                 }
8632
8633                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8634                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8635                     curscore = -curscore;
8636                 }
8637
8638                 cpstats.depth = plylev;
8639                 cpstats.nodes = nodes;
8640                 cpstats.time = time;
8641                 cpstats.score = curscore;
8642                 cpstats.got_only_move = 0;
8643                 cpstats.movelist[0] = '\0';
8644
8645                 if (buf1[0] != NULLCHAR) {
8646                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8647                 }
8648
8649                 cpstats.ok_to_send = 0;
8650                 cpstats.line_is_book = 0;
8651                 cpstats.nr_moves = 0;
8652                 cpstats.moves_left = 0;
8653
8654                 SendProgramStatsToFrontend( cps, &cpstats );
8655             }
8656         }
8657     }
8658 }
8659
8660
8661 /* Parse a game score from the character string "game", and
8662    record it as the history of the current game.  The game
8663    score is NOT assumed to start from the standard position.
8664    The display is not updated in any way.
8665    */
8666 void
8667 ParseGameHistory(game)
8668      char *game;
8669 {
8670     ChessMove moveType;
8671     int fromX, fromY, toX, toY, boardIndex;
8672     char promoChar;
8673     char *p, *q;
8674     char buf[MSG_SIZ];
8675
8676     if (appData.debugMode)
8677       fprintf(debugFP, "Parsing game history: %s\n", game);
8678
8679     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8680     gameInfo.site = StrSave(appData.icsHost);
8681     gameInfo.date = PGNDate();
8682     gameInfo.round = StrSave("-");
8683
8684     /* Parse out names of players */
8685     while (*game == ' ') game++;
8686     p = buf;
8687     while (*game != ' ') *p++ = *game++;
8688     *p = NULLCHAR;
8689     gameInfo.white = StrSave(buf);
8690     while (*game == ' ') game++;
8691     p = buf;
8692     while (*game != ' ' && *game != '\n') *p++ = *game++;
8693     *p = NULLCHAR;
8694     gameInfo.black = StrSave(buf);
8695
8696     /* Parse moves */
8697     boardIndex = blackPlaysFirst ? 1 : 0;
8698     yynewstr(game);
8699     for (;;) {
8700         yyboardindex = boardIndex;
8701         moveType = (ChessMove) Myylex();
8702         switch (moveType) {
8703           case IllegalMove:             /* maybe suicide chess, etc. */
8704   if (appData.debugMode) {
8705     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8706     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8707     setbuf(debugFP, NULL);
8708   }
8709           case WhitePromotion:
8710           case BlackPromotion:
8711           case WhiteNonPromotion:
8712           case BlackNonPromotion:
8713           case NormalMove:
8714           case WhiteCapturesEnPassant:
8715           case BlackCapturesEnPassant:
8716           case WhiteKingSideCastle:
8717           case WhiteQueenSideCastle:
8718           case BlackKingSideCastle:
8719           case BlackQueenSideCastle:
8720           case WhiteKingSideCastleWild:
8721           case WhiteQueenSideCastleWild:
8722           case BlackKingSideCastleWild:
8723           case BlackQueenSideCastleWild:
8724           /* PUSH Fabien */
8725           case WhiteHSideCastleFR:
8726           case WhiteASideCastleFR:
8727           case BlackHSideCastleFR:
8728           case BlackASideCastleFR:
8729           /* POP Fabien */
8730             fromX = currentMoveString[0] - AAA;
8731             fromY = currentMoveString[1] - ONE;
8732             toX = currentMoveString[2] - AAA;
8733             toY = currentMoveString[3] - ONE;
8734             promoChar = currentMoveString[4];
8735             break;
8736           case WhiteDrop:
8737           case BlackDrop:
8738             fromX = moveType == WhiteDrop ?
8739               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8740             (int) CharToPiece(ToLower(currentMoveString[0]));
8741             fromY = DROP_RANK;
8742             toX = currentMoveString[2] - AAA;
8743             toY = currentMoveString[3] - ONE;
8744             promoChar = NULLCHAR;
8745             break;
8746           case AmbiguousMove:
8747             /* bug? */
8748             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8749   if (appData.debugMode) {
8750     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8751     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8752     setbuf(debugFP, NULL);
8753   }
8754             DisplayError(buf, 0);
8755             return;
8756           case ImpossibleMove:
8757             /* bug? */
8758             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8759   if (appData.debugMode) {
8760     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8761     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8762     setbuf(debugFP, NULL);
8763   }
8764             DisplayError(buf, 0);
8765             return;
8766           case EndOfFile:
8767             if (boardIndex < backwardMostMove) {
8768                 /* Oops, gap.  How did that happen? */
8769                 DisplayError(_("Gap in move list"), 0);
8770                 return;
8771             }
8772             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8773             if (boardIndex > forwardMostMove) {
8774                 forwardMostMove = boardIndex;
8775             }
8776             return;
8777           case ElapsedTime:
8778             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8779                 strcat(parseList[boardIndex-1], " ");
8780                 strcat(parseList[boardIndex-1], yy_text);
8781             }
8782             continue;
8783           case Comment:
8784           case PGNTag:
8785           case NAG:
8786           default:
8787             /* ignore */
8788             continue;
8789           case WhiteWins:
8790           case BlackWins:
8791           case GameIsDrawn:
8792           case GameUnfinished:
8793             if (gameMode == IcsExamining) {
8794                 if (boardIndex < backwardMostMove) {
8795                     /* Oops, gap.  How did that happen? */
8796                     return;
8797                 }
8798                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8799                 return;
8800             }
8801             gameInfo.result = moveType;
8802             p = strchr(yy_text, '{');
8803             if (p == NULL) p = strchr(yy_text, '(');
8804             if (p == NULL) {
8805                 p = yy_text;
8806                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8807             } else {
8808                 q = strchr(p, *p == '{' ? '}' : ')');
8809                 if (q != NULL) *q = NULLCHAR;
8810                 p++;
8811             }
8812             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8813             gameInfo.resultDetails = StrSave(p);
8814             continue;
8815         }
8816         if (boardIndex >= forwardMostMove &&
8817             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8818             backwardMostMove = blackPlaysFirst ? 1 : 0;
8819             return;
8820         }
8821         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8822                                  fromY, fromX, toY, toX, promoChar,
8823                                  parseList[boardIndex]);
8824         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8825         /* currentMoveString is set as a side-effect of yylex */
8826         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8827         strcat(moveList[boardIndex], "\n");
8828         boardIndex++;
8829         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8830         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8831           case MT_NONE:
8832           case MT_STALEMATE:
8833           default:
8834             break;
8835           case MT_CHECK:
8836             if(gameInfo.variant != VariantShogi)
8837                 strcat(parseList[boardIndex - 1], "+");
8838             break;
8839           case MT_CHECKMATE:
8840           case MT_STAINMATE:
8841             strcat(parseList[boardIndex - 1], "#");
8842             break;
8843         }
8844     }
8845 }
8846
8847
8848 /* Apply a move to the given board  */
8849 void
8850 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8851      int fromX, fromY, toX, toY;
8852      int promoChar;
8853      Board board;
8854 {
8855   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8856   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8857
8858     /* [HGM] compute & store e.p. status and castling rights for new position */
8859     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8860
8861       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8862       oldEP = (signed char)board[EP_STATUS];
8863       board[EP_STATUS] = EP_NONE;
8864
8865       if( board[toY][toX] != EmptySquare )
8866            board[EP_STATUS] = EP_CAPTURE;
8867
8868   if (fromY == DROP_RANK) {
8869         /* must be first */
8870         piece = board[toY][toX] = (ChessSquare) fromX;
8871   } else {
8872       int i;
8873
8874       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8875            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8876                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8877       } else
8878       if( board[fromY][fromX] == WhitePawn ) {
8879            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8880                board[EP_STATUS] = EP_PAWN_MOVE;
8881            if( toY-fromY==2) {
8882                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8883                         gameInfo.variant != VariantBerolina || toX < fromX)
8884                       board[EP_STATUS] = toX | berolina;
8885                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8886                         gameInfo.variant != VariantBerolina || toX > fromX)
8887                       board[EP_STATUS] = toX;
8888            }
8889       } else
8890       if( board[fromY][fromX] == BlackPawn ) {
8891            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8892                board[EP_STATUS] = EP_PAWN_MOVE;
8893            if( toY-fromY== -2) {
8894                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8895                         gameInfo.variant != VariantBerolina || toX < fromX)
8896                       board[EP_STATUS] = toX | berolina;
8897                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8898                         gameInfo.variant != VariantBerolina || toX > fromX)
8899                       board[EP_STATUS] = toX;
8900            }
8901        }
8902
8903        for(i=0; i<nrCastlingRights; i++) {
8904            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8905               board[CASTLING][i] == toX   && castlingRank[i] == toY
8906              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8907        }
8908
8909      if (fromX == toX && fromY == toY) return;
8910
8911      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8912      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8913      if(gameInfo.variant == VariantKnightmate)
8914          king += (int) WhiteUnicorn - (int) WhiteKing;
8915
8916     /* Code added by Tord: */
8917     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8918     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8919         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8920       board[fromY][fromX] = EmptySquare;
8921       board[toY][toX] = EmptySquare;
8922       if((toX > fromX) != (piece == WhiteRook)) {
8923         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8924       } else {
8925         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8926       }
8927     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8928                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8929       board[fromY][fromX] = EmptySquare;
8930       board[toY][toX] = EmptySquare;
8931       if((toX > fromX) != (piece == BlackRook)) {
8932         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8933       } else {
8934         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8935       }
8936     /* End of code added by Tord */
8937
8938     } else if (board[fromY][fromX] == king
8939         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8940         && toY == fromY && toX > fromX+1) {
8941         board[fromY][fromX] = EmptySquare;
8942         board[toY][toX] = king;
8943         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8944         board[fromY][BOARD_RGHT-1] = EmptySquare;
8945     } else if (board[fromY][fromX] == king
8946         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8947                && toY == fromY && toX < fromX-1) {
8948         board[fromY][fromX] = EmptySquare;
8949         board[toY][toX] = king;
8950         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8951         board[fromY][BOARD_LEFT] = EmptySquare;
8952     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8953                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8954                && toY >= BOARD_HEIGHT-promoRank
8955                ) {
8956         /* white pawn promotion */
8957         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8958         if (board[toY][toX] == EmptySquare) {
8959             board[toY][toX] = WhiteQueen;
8960         }
8961         if(gameInfo.variant==VariantBughouse ||
8962            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8963             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8964         board[fromY][fromX] = EmptySquare;
8965     } else if ((fromY == BOARD_HEIGHT-4)
8966                && (toX != fromX)
8967                && gameInfo.variant != VariantXiangqi
8968                && gameInfo.variant != VariantBerolina
8969                && (board[fromY][fromX] == WhitePawn)
8970                && (board[toY][toX] == EmptySquare)) {
8971         board[fromY][fromX] = EmptySquare;
8972         board[toY][toX] = WhitePawn;
8973         captured = board[toY - 1][toX];
8974         board[toY - 1][toX] = EmptySquare;
8975     } else if ((fromY == BOARD_HEIGHT-4)
8976                && (toX == fromX)
8977                && gameInfo.variant == VariantBerolina
8978                && (board[fromY][fromX] == WhitePawn)
8979                && (board[toY][toX] == EmptySquare)) {
8980         board[fromY][fromX] = EmptySquare;
8981         board[toY][toX] = WhitePawn;
8982         if(oldEP & EP_BEROLIN_A) {
8983                 captured = board[fromY][fromX-1];
8984                 board[fromY][fromX-1] = EmptySquare;
8985         }else{  captured = board[fromY][fromX+1];
8986                 board[fromY][fromX+1] = EmptySquare;
8987         }
8988     } else if (board[fromY][fromX] == king
8989         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8990                && toY == fromY && toX > fromX+1) {
8991         board[fromY][fromX] = EmptySquare;
8992         board[toY][toX] = king;
8993         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8994         board[fromY][BOARD_RGHT-1] = EmptySquare;
8995     } else if (board[fromY][fromX] == king
8996         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8997                && toY == fromY && toX < fromX-1) {
8998         board[fromY][fromX] = EmptySquare;
8999         board[toY][toX] = king;
9000         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9001         board[fromY][BOARD_LEFT] = EmptySquare;
9002     } else if (fromY == 7 && fromX == 3
9003                && board[fromY][fromX] == BlackKing
9004                && toY == 7 && toX == 5) {
9005         board[fromY][fromX] = EmptySquare;
9006         board[toY][toX] = BlackKing;
9007         board[fromY][7] = EmptySquare;
9008         board[toY][4] = BlackRook;
9009     } else if (fromY == 7 && fromX == 3
9010                && board[fromY][fromX] == BlackKing
9011                && toY == 7 && toX == 1) {
9012         board[fromY][fromX] = EmptySquare;
9013         board[toY][toX] = BlackKing;
9014         board[fromY][0] = EmptySquare;
9015         board[toY][2] = BlackRook;
9016     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9017                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9018                && toY < promoRank
9019                ) {
9020         /* black pawn promotion */
9021         board[toY][toX] = CharToPiece(ToLower(promoChar));
9022         if (board[toY][toX] == EmptySquare) {
9023             board[toY][toX] = BlackQueen;
9024         }
9025         if(gameInfo.variant==VariantBughouse ||
9026            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9027             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9028         board[fromY][fromX] = EmptySquare;
9029     } else if ((fromY == 3)
9030                && (toX != fromX)
9031                && gameInfo.variant != VariantXiangqi
9032                && gameInfo.variant != VariantBerolina
9033                && (board[fromY][fromX] == BlackPawn)
9034                && (board[toY][toX] == EmptySquare)) {
9035         board[fromY][fromX] = EmptySquare;
9036         board[toY][toX] = BlackPawn;
9037         captured = board[toY + 1][toX];
9038         board[toY + 1][toX] = EmptySquare;
9039     } else if ((fromY == 3)
9040                && (toX == fromX)
9041                && gameInfo.variant == VariantBerolina
9042                && (board[fromY][fromX] == BlackPawn)
9043                && (board[toY][toX] == EmptySquare)) {
9044         board[fromY][fromX] = EmptySquare;
9045         board[toY][toX] = BlackPawn;
9046         if(oldEP & EP_BEROLIN_A) {
9047                 captured = board[fromY][fromX-1];
9048                 board[fromY][fromX-1] = EmptySquare;
9049         }else{  captured = board[fromY][fromX+1];
9050                 board[fromY][fromX+1] = EmptySquare;
9051         }
9052     } else {
9053         board[toY][toX] = board[fromY][fromX];
9054         board[fromY][fromX] = EmptySquare;
9055     }
9056   }
9057
9058     if (gameInfo.holdingsWidth != 0) {
9059
9060       /* !!A lot more code needs to be written to support holdings  */
9061       /* [HGM] OK, so I have written it. Holdings are stored in the */
9062       /* penultimate board files, so they are automaticlly stored   */
9063       /* in the game history.                                       */
9064       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9065                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9066         /* Delete from holdings, by decreasing count */
9067         /* and erasing image if necessary            */
9068         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9069         if(p < (int) BlackPawn) { /* white drop */
9070              p -= (int)WhitePawn;
9071                  p = PieceToNumber((ChessSquare)p);
9072              if(p >= gameInfo.holdingsSize) p = 0;
9073              if(--board[p][BOARD_WIDTH-2] <= 0)
9074                   board[p][BOARD_WIDTH-1] = EmptySquare;
9075              if((int)board[p][BOARD_WIDTH-2] < 0)
9076                         board[p][BOARD_WIDTH-2] = 0;
9077         } else {                  /* black drop */
9078              p -= (int)BlackPawn;
9079                  p = PieceToNumber((ChessSquare)p);
9080              if(p >= gameInfo.holdingsSize) p = 0;
9081              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9082                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9083              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9084                         board[BOARD_HEIGHT-1-p][1] = 0;
9085         }
9086       }
9087       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9088           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9089         /* [HGM] holdings: Add to holdings, if holdings exist */
9090         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9091                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9092                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9093         }
9094         p = (int) captured;
9095         if (p >= (int) BlackPawn) {
9096           p -= (int)BlackPawn;
9097           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9098                   /* in Shogi restore piece to its original  first */
9099                   captured = (ChessSquare) (DEMOTED captured);
9100                   p = DEMOTED p;
9101           }
9102           p = PieceToNumber((ChessSquare)p);
9103           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9104           board[p][BOARD_WIDTH-2]++;
9105           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9106         } else {
9107           p -= (int)WhitePawn;
9108           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9109                   captured = (ChessSquare) (DEMOTED captured);
9110                   p = DEMOTED p;
9111           }
9112           p = PieceToNumber((ChessSquare)p);
9113           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9114           board[BOARD_HEIGHT-1-p][1]++;
9115           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9116         }
9117       }
9118     } else if (gameInfo.variant == VariantAtomic) {
9119       if (captured != EmptySquare) {
9120         int y, x;
9121         for (y = toY-1; y <= toY+1; y++) {
9122           for (x = toX-1; x <= toX+1; x++) {
9123             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9124                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9125               board[y][x] = EmptySquare;
9126             }
9127           }
9128         }
9129         board[toY][toX] = EmptySquare;
9130       }
9131     }
9132     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9133         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9134     } else
9135     if(promoChar == '+') {
9136         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9137         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9138     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9139         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9140     }
9141     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9142                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9143         // [HGM] superchess: take promotion piece out of holdings
9144         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9145         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9146             if(!--board[k][BOARD_WIDTH-2])
9147                 board[k][BOARD_WIDTH-1] = EmptySquare;
9148         } else {
9149             if(!--board[BOARD_HEIGHT-1-k][1])
9150                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9151         }
9152     }
9153
9154 }
9155
9156 /* Updates forwardMostMove */
9157 void
9158 MakeMove(fromX, fromY, toX, toY, promoChar)
9159      int fromX, fromY, toX, toY;
9160      int promoChar;
9161 {
9162 //    forwardMostMove++; // [HGM] bare: moved downstream
9163
9164     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9165         int timeLeft; static int lastLoadFlag=0; int king, piece;
9166         piece = boards[forwardMostMove][fromY][fromX];
9167         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9168         if(gameInfo.variant == VariantKnightmate)
9169             king += (int) WhiteUnicorn - (int) WhiteKing;
9170         if(forwardMostMove == 0) {
9171             if(blackPlaysFirst)
9172                 fprintf(serverMoves, "%s;", second.tidy);
9173             fprintf(serverMoves, "%s;", first.tidy);
9174             if(!blackPlaysFirst)
9175                 fprintf(serverMoves, "%s;", second.tidy);
9176         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9177         lastLoadFlag = loadFlag;
9178         // print base move
9179         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9180         // print castling suffix
9181         if( toY == fromY && piece == king ) {
9182             if(toX-fromX > 1)
9183                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9184             if(fromX-toX >1)
9185                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9186         }
9187         // e.p. suffix
9188         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9189              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9190              boards[forwardMostMove][toY][toX] == EmptySquare
9191              && fromX != toX && fromY != toY)
9192                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9193         // promotion suffix
9194         if(promoChar != NULLCHAR)
9195                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9196         if(!loadFlag) {
9197             fprintf(serverMoves, "/%d/%d",
9198                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9199             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9200             else                      timeLeft = blackTimeRemaining/1000;
9201             fprintf(serverMoves, "/%d", timeLeft);
9202         }
9203         fflush(serverMoves);
9204     }
9205
9206     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9207       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9208                         0, 1);
9209       return;
9210     }
9211     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9212     if (commentList[forwardMostMove+1] != NULL) {
9213         free(commentList[forwardMostMove+1]);
9214         commentList[forwardMostMove+1] = NULL;
9215     }
9216     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9217     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9218     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9219     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9220     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9221     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9222     gameInfo.result = GameUnfinished;
9223     if (gameInfo.resultDetails != NULL) {
9224         free(gameInfo.resultDetails);
9225         gameInfo.resultDetails = NULL;
9226     }
9227     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9228                               moveList[forwardMostMove - 1]);
9229     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9230                              PosFlags(forwardMostMove - 1),
9231                              fromY, fromX, toY, toX, promoChar,
9232                              parseList[forwardMostMove - 1]);
9233     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9234       case MT_NONE:
9235       case MT_STALEMATE:
9236       default:
9237         break;
9238       case MT_CHECK:
9239         if(gameInfo.variant != VariantShogi)
9240             strcat(parseList[forwardMostMove - 1], "+");
9241         break;
9242       case MT_CHECKMATE:
9243       case MT_STAINMATE:
9244         strcat(parseList[forwardMostMove - 1], "#");
9245         break;
9246     }
9247     if (appData.debugMode) {
9248         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9249     }
9250
9251 }
9252
9253 /* Updates currentMove if not pausing */
9254 void
9255 ShowMove(fromX, fromY, toX, toY)
9256 {
9257     int instant = (gameMode == PlayFromGameFile) ?
9258         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9259     if(appData.noGUI) return;
9260     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9261         if (!instant) {
9262             if (forwardMostMove == currentMove + 1) {
9263                 AnimateMove(boards[forwardMostMove - 1],
9264                             fromX, fromY, toX, toY);
9265             }
9266             if (appData.highlightLastMove) {
9267                 SetHighlights(fromX, fromY, toX, toY);
9268             }
9269         }
9270         currentMove = forwardMostMove;
9271     }
9272
9273     if (instant) return;
9274
9275     DisplayMove(currentMove - 1);
9276     DrawPosition(FALSE, boards[currentMove]);
9277     DisplayBothClocks();
9278     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9279     DisplayBook(currentMove);
9280 }
9281
9282 void SendEgtPath(ChessProgramState *cps)
9283 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9284         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9285
9286         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9287
9288         while(*p) {
9289             char c, *q = name+1, *r, *s;
9290
9291             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9292             while(*p && *p != ',') *q++ = *p++;
9293             *q++ = ':'; *q = 0;
9294             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9295                 strcmp(name, ",nalimov:") == 0 ) {
9296                 // take nalimov path from the menu-changeable option first, if it is defined
9297               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9298                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9299             } else
9300             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9301                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9302                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9303                 s = r = StrStr(s, ":") + 1; // beginning of path info
9304                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9305                 c = *r; *r = 0;             // temporarily null-terminate path info
9306                     *--q = 0;               // strip of trailig ':' from name
9307                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9308                 *r = c;
9309                 SendToProgram(buf,cps);     // send egtbpath command for this format
9310             }
9311             if(*p == ',') p++; // read away comma to position for next format name
9312         }
9313 }
9314
9315 void
9316 InitChessProgram(cps, setup)
9317      ChessProgramState *cps;
9318      int setup; /* [HGM] needed to setup FRC opening position */
9319 {
9320     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9321     if (appData.noChessProgram) return;
9322     hintRequested = FALSE;
9323     bookRequested = FALSE;
9324
9325     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9326     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9327     if(cps->memSize) { /* [HGM] memory */
9328       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9329         SendToProgram(buf, cps);
9330     }
9331     SendEgtPath(cps); /* [HGM] EGT */
9332     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9333       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9334         SendToProgram(buf, cps);
9335     }
9336
9337     SendToProgram(cps->initString, cps);
9338     if (gameInfo.variant != VariantNormal &&
9339         gameInfo.variant != VariantLoadable
9340         /* [HGM] also send variant if board size non-standard */
9341         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9342                                             ) {
9343       char *v = VariantName(gameInfo.variant);
9344       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9345         /* [HGM] in protocol 1 we have to assume all variants valid */
9346         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9347         DisplayFatalError(buf, 0, 1);
9348         return;
9349       }
9350
9351       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9352       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9353       if( gameInfo.variant == VariantXiangqi )
9354            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9355       if( gameInfo.variant == VariantShogi )
9356            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9357       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9358            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9359       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9360           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9361            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9362       if( gameInfo.variant == VariantCourier )
9363            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9364       if( gameInfo.variant == VariantSuper )
9365            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9366       if( gameInfo.variant == VariantGreat )
9367            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9368       if( gameInfo.variant == VariantSChess )
9369            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9370
9371       if(overruled) {
9372         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9373                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9374            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9375            if(StrStr(cps->variants, b) == NULL) {
9376                // specific sized variant not known, check if general sizing allowed
9377                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9378                    if(StrStr(cps->variants, "boardsize") == NULL) {
9379                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9380                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9381                        DisplayFatalError(buf, 0, 1);
9382                        return;
9383                    }
9384                    /* [HGM] here we really should compare with the maximum supported board size */
9385                }
9386            }
9387       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9388       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9389       SendToProgram(buf, cps);
9390     }
9391     currentlyInitializedVariant = gameInfo.variant;
9392
9393     /* [HGM] send opening position in FRC to first engine */
9394     if(setup) {
9395           SendToProgram("force\n", cps);
9396           SendBoard(cps, 0);
9397           /* engine is now in force mode! Set flag to wake it up after first move. */
9398           setboardSpoiledMachineBlack = 1;
9399     }
9400
9401     if (cps->sendICS) {
9402       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9403       SendToProgram(buf, cps);
9404     }
9405     cps->maybeThinking = FALSE;
9406     cps->offeredDraw = 0;
9407     if (!appData.icsActive) {
9408         SendTimeControl(cps, movesPerSession, timeControl,
9409                         timeIncrement, appData.searchDepth,
9410                         searchTime);
9411     }
9412     if (appData.showThinking
9413         // [HGM] thinking: four options require thinking output to be sent
9414         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9415                                 ) {
9416         SendToProgram("post\n", cps);
9417     }
9418     SendToProgram("hard\n", cps);
9419     if (!appData.ponderNextMove) {
9420         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9421            it without being sure what state we are in first.  "hard"
9422            is not a toggle, so that one is OK.
9423          */
9424         SendToProgram("easy\n", cps);
9425     }
9426     if (cps->usePing) {
9427       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9428       SendToProgram(buf, cps);
9429     }
9430     cps->initDone = TRUE;
9431 }
9432
9433
9434 void
9435 StartChessProgram(cps)
9436      ChessProgramState *cps;
9437 {
9438     char buf[MSG_SIZ];
9439     int err;
9440
9441     if (appData.noChessProgram) return;
9442     cps->initDone = FALSE;
9443
9444     if (strcmp(cps->host, "localhost") == 0) {
9445         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9446     } else if (*appData.remoteShell == NULLCHAR) {
9447         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9448     } else {
9449         if (*appData.remoteUser == NULLCHAR) {
9450           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9451                     cps->program);
9452         } else {
9453           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9454                     cps->host, appData.remoteUser, cps->program);
9455         }
9456         err = StartChildProcess(buf, "", &cps->pr);
9457     }
9458
9459     if (err != 0) {
9460       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9461         DisplayFatalError(buf, err, 1);
9462         cps->pr = NoProc;
9463         cps->isr = NULL;
9464         return;
9465     }
9466
9467     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9468     if (cps->protocolVersion > 1) {
9469       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9470       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9471       cps->comboCnt = 0;  //                and values of combo boxes
9472       SendToProgram(buf, cps);
9473     } else {
9474       SendToProgram("xboard\n", cps);
9475     }
9476 }
9477
9478 void
9479 TwoMachinesEventIfReady P((void))
9480 {
9481   static int curMess = 0;
9482   if (first.lastPing != first.lastPong) {
9483     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9484     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9485     return;
9486   }
9487   if (second.lastPing != second.lastPong) {
9488     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9489     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9490     return;
9491   }
9492   DisplayMessage("", ""); curMess = 0;
9493   ThawUI();
9494   TwoMachinesEvent();
9495 }
9496
9497 int
9498 CreateTourney(char *name)
9499 {
9500         FILE *f;
9501         if(name[0] == NULLCHAR) return 0;
9502         f = fopen(appData.tourneyFile, "r");
9503         if(f) { // file exists
9504             ParseArgsFromFile(f); // parse it
9505         } else {
9506             f = fopen(appData.tourneyFile, "w");
9507             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9508                 // create a file with tournament description
9509                 fprintf(f, "-participants {%s}\n", appData.participants);
9510                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9511                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9512                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9513                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9514                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9515                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9516                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9517                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9518                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9519                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9520                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9521                 if(searchTime > 0)
9522                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9523                 else {
9524                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9525                         fprintf(f, "-tc %s\n", appData.timeControl);
9526                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9527                 }
9528                 fprintf(f, "-results \"\"\n");
9529             }
9530         }
9531         fclose(f);
9532         appData.noChessProgram = FALSE;
9533         appData.clockMode = TRUE;
9534         SetGNUMode();
9535         return 1;
9536 }
9537
9538 #define MAXENGINES 1000
9539 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9540
9541 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9542 {
9543     char buf[MSG_SIZ], *p, *q;
9544     int i=1;
9545     while(*names) {
9546         p = names; q = buf;
9547         while(*p && *p != '\n') *q++ = *p++;
9548         *q = 0;
9549         if(engineList[i]) free(engineList[i]);
9550         engineList[i] = strdup(buf);
9551         if(*p == '\n') p++;
9552         TidyProgramName(engineList[i], "localhost", buf);
9553         if(engineMnemonic[i]) free(engineMnemonic[i]);
9554         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9555             strcat(buf, " (");
9556             sscanf(q + 8, "%s", buf + strlen(buf));
9557             strcat(buf, ")");
9558         }
9559         engineMnemonic[i] = strdup(buf);
9560         names = p; i++;
9561       if(i > MAXENGINES - 2) break;
9562     }
9563     engineList[i] = NULL;
9564 }
9565
9566 // following implemented as macro to avoid type limitations
9567 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9568
9569 void SwapEngines(int n)
9570 {   // swap settings for first engine and other engine (so far only some selected options)
9571     int h;
9572     char *p;
9573     if(n == 0) return;
9574     SWAP(directory, p)
9575     SWAP(chessProgram, p)
9576     SWAP(isUCI, h)
9577     SWAP(hasOwnBookUCI, h)
9578     SWAP(protocolVersion, h)
9579     SWAP(reuse, h)
9580     SWAP(scoreIsAbsolute, h)
9581     SWAP(timeOdds, h)
9582     SWAP(logo, p)
9583     SWAP(pgnName, p)
9584 }
9585
9586 void
9587 SetPlayer(int player)
9588 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9589     int i;
9590     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9591     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9592     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9593     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9594     if(mnemonic[i]) {
9595         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9596         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9597         ParseArgsFromString(buf);
9598     }
9599     free(engineName);
9600 }
9601
9602 int
9603 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9604 {   // determine players from game number
9605     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9606
9607     if(appData.tourneyType == 0) {
9608         roundsPerCycle = (nPlayers - 1) | 1;
9609         pairingsPerRound = nPlayers / 2;
9610     } else if(appData.tourneyType > 0) {
9611         roundsPerCycle = nPlayers - appData.tourneyType;
9612         pairingsPerRound = appData.tourneyType;
9613     }
9614     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9615     gamesPerCycle = gamesPerRound * roundsPerCycle;
9616     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9617     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9618     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9619     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9620     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9621     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9622
9623     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9624     if(appData.roundSync) *syncInterval = gamesPerRound;
9625
9626     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9627
9628     if(appData.tourneyType == 0) {
9629         if(curPairing == (nPlayers-1)/2 ) {
9630             *whitePlayer = curRound;
9631             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9632         } else {
9633             *whitePlayer = curRound - pairingsPerRound + curPairing;
9634             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9635             *blackPlayer = curRound + pairingsPerRound - curPairing;
9636             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9637         }
9638     } else if(appData.tourneyType > 0) {
9639         *whitePlayer = curPairing;
9640         *blackPlayer = curRound + appData.tourneyType;
9641     }
9642
9643     // take care of white/black alternation per round. 
9644     // For cycles and games this is already taken care of by default, derived from matchGame!
9645     return curRound & 1;
9646 }
9647
9648 int
9649 NextTourneyGame(int nr, int *swapColors)
9650 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9651     char *p, *q;
9652     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9653     FILE *tf;
9654     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9655     tf = fopen(appData.tourneyFile, "r");
9656     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9657     ParseArgsFromFile(tf); fclose(tf);
9658     InitTimeControls(); // TC might be altered from tourney file
9659
9660     p = appData.participants;
9661     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9662     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9663
9664     if(syncInterval) {
9665         p = q = appData.results;
9666         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9667         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9668             DisplayMessage(_("Waiting for other game(s)"),"");
9669             waitingForGame = TRUE;
9670             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9671             return 0;
9672         }
9673         waitingForGame = FALSE;
9674     }
9675
9676     if(first.pr != NoProc) return 1; // engines already loaded
9677
9678     // redefine engines, engine dir, etc.
9679     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9680     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9681     SwapEngines(1);
9682     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9683     SwapEngines(1);         // and make that valid for second engine by swapping
9684     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9685     InitEngine(&second, 1);
9686     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9687     return 1;
9688 }
9689
9690 void
9691 NextMatchGame()
9692 {   // performs game initialization that does not invoke engines, and then tries to start the game
9693     int firstWhite, swapColors = 0;
9694     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9695     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9696     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9697     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9698     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9699     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9700     Reset(FALSE, first.pr != NoProc);
9701     appData.noChessProgram = FALSE;
9702     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9703     TwoMachinesEvent();
9704 }
9705
9706 void UserAdjudicationEvent( int result )
9707 {
9708     ChessMove gameResult = GameIsDrawn;
9709
9710     if( result > 0 ) {
9711         gameResult = WhiteWins;
9712     }
9713     else if( result < 0 ) {
9714         gameResult = BlackWins;
9715     }
9716
9717     if( gameMode == TwoMachinesPlay ) {
9718         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9719     }
9720 }
9721
9722
9723 // [HGM] save: calculate checksum of game to make games easily identifiable
9724 int StringCheckSum(char *s)
9725 {
9726         int i = 0;
9727         if(s==NULL) return 0;
9728         while(*s) i = i*259 + *s++;
9729         return i;
9730 }
9731
9732 int GameCheckSum()
9733 {
9734         int i, sum=0;
9735         for(i=backwardMostMove; i<forwardMostMove; i++) {
9736                 sum += pvInfoList[i].depth;
9737                 sum += StringCheckSum(parseList[i]);
9738                 sum += StringCheckSum(commentList[i]);
9739                 sum *= 261;
9740         }
9741         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9742         return sum + StringCheckSum(commentList[i]);
9743 } // end of save patch
9744
9745 void
9746 GameEnds(result, resultDetails, whosays)
9747      ChessMove result;
9748      char *resultDetails;
9749      int whosays;
9750 {
9751     GameMode nextGameMode;
9752     int isIcsGame;
9753     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9754
9755     if(endingGame) return; /* [HGM] crash: forbid recursion */
9756     endingGame = 1;
9757     if(twoBoards) { // [HGM] dual: switch back to one board
9758         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9759         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9760     }
9761     if (appData.debugMode) {
9762       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9763               result, resultDetails ? resultDetails : "(null)", whosays);
9764     }
9765
9766     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9767
9768     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9769         /* If we are playing on ICS, the server decides when the
9770            game is over, but the engine can offer to draw, claim
9771            a draw, or resign.
9772          */
9773 #if ZIPPY
9774         if (appData.zippyPlay && first.initDone) {
9775             if (result == GameIsDrawn) {
9776                 /* In case draw still needs to be claimed */
9777                 SendToICS(ics_prefix);
9778                 SendToICS("draw\n");
9779             } else if (StrCaseStr(resultDetails, "resign")) {
9780                 SendToICS(ics_prefix);
9781                 SendToICS("resign\n");
9782             }
9783         }
9784 #endif
9785         endingGame = 0; /* [HGM] crash */
9786         return;
9787     }
9788
9789     /* If we're loading the game from a file, stop */
9790     if (whosays == GE_FILE) {
9791       (void) StopLoadGameTimer();
9792       gameFileFP = NULL;
9793     }
9794
9795     /* Cancel draw offers */
9796     first.offeredDraw = second.offeredDraw = 0;
9797
9798     /* If this is an ICS game, only ICS can really say it's done;
9799        if not, anyone can. */
9800     isIcsGame = (gameMode == IcsPlayingWhite ||
9801                  gameMode == IcsPlayingBlack ||
9802                  gameMode == IcsObserving    ||
9803                  gameMode == IcsExamining);
9804
9805     if (!isIcsGame || whosays == GE_ICS) {
9806         /* OK -- not an ICS game, or ICS said it was done */
9807         StopClocks();
9808         if (!isIcsGame && !appData.noChessProgram)
9809           SetUserThinkingEnables();
9810
9811         /* [HGM] if a machine claims the game end we verify this claim */
9812         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9813             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9814                 char claimer;
9815                 ChessMove trueResult = (ChessMove) -1;
9816
9817                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9818                                             first.twoMachinesColor[0] :
9819                                             second.twoMachinesColor[0] ;
9820
9821                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9822                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9823                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9824                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9825                 } else
9826                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9827                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9828                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9829                 } else
9830                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9831                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9832                 }
9833
9834                 // now verify win claims, but not in drop games, as we don't understand those yet
9835                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9836                                                  || gameInfo.variant == VariantGreat) &&
9837                     (result == WhiteWins && claimer == 'w' ||
9838                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9839                       if (appData.debugMode) {
9840                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9841                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9842                       }
9843                       if(result != trueResult) {
9844                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9845                               result = claimer == 'w' ? BlackWins : WhiteWins;
9846                               resultDetails = buf;
9847                       }
9848                 } else
9849                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9850                     && (forwardMostMove <= backwardMostMove ||
9851                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9852                         (claimer=='b')==(forwardMostMove&1))
9853                                                                                   ) {
9854                       /* [HGM] verify: draws that were not flagged are false claims */
9855                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9856                       result = claimer == 'w' ? BlackWins : WhiteWins;
9857                       resultDetails = buf;
9858                 }
9859                 /* (Claiming a loss is accepted no questions asked!) */
9860             }
9861             /* [HGM] bare: don't allow bare King to win */
9862             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9863                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9864                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9865                && result != GameIsDrawn)
9866             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9867                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9868                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9869                         if(p >= 0 && p <= (int)WhiteKing) k++;
9870                 }
9871                 if (appData.debugMode) {
9872                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9873                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9874                 }
9875                 if(k <= 1) {
9876                         result = GameIsDrawn;
9877                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9878                         resultDetails = buf;
9879                 }
9880             }
9881         }
9882
9883
9884         if(serverMoves != NULL && !loadFlag) { char c = '=';
9885             if(result==WhiteWins) c = '+';
9886             if(result==BlackWins) c = '-';
9887             if(resultDetails != NULL)
9888                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9889         }
9890         if (resultDetails != NULL) {
9891             gameInfo.result = result;
9892             gameInfo.resultDetails = StrSave(resultDetails);
9893
9894             /* display last move only if game was not loaded from file */
9895             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9896                 DisplayMove(currentMove - 1);
9897
9898             if (forwardMostMove != 0) {
9899                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9900                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9901                                                                 ) {
9902                     if (*appData.saveGameFile != NULLCHAR) {
9903                         SaveGameToFile(appData.saveGameFile, TRUE);
9904                     } else if (appData.autoSaveGames) {
9905                         AutoSaveGame();
9906                     }
9907                     if (*appData.savePositionFile != NULLCHAR) {
9908                         SavePositionToFile(appData.savePositionFile);
9909                     }
9910                 }
9911             }
9912
9913             /* Tell program how game ended in case it is learning */
9914             /* [HGM] Moved this to after saving the PGN, just in case */
9915             /* engine died and we got here through time loss. In that */
9916             /* case we will get a fatal error writing the pipe, which */
9917             /* would otherwise lose us the PGN.                       */
9918             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9919             /* output during GameEnds should never be fatal anymore   */
9920             if (gameMode == MachinePlaysWhite ||
9921                 gameMode == MachinePlaysBlack ||
9922                 gameMode == TwoMachinesPlay ||
9923                 gameMode == IcsPlayingWhite ||
9924                 gameMode == IcsPlayingBlack ||
9925                 gameMode == BeginningOfGame) {
9926                 char buf[MSG_SIZ];
9927                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9928                         resultDetails);
9929                 if (first.pr != NoProc) {
9930                     SendToProgram(buf, &first);
9931                 }
9932                 if (second.pr != NoProc &&
9933                     gameMode == TwoMachinesPlay) {
9934                     SendToProgram(buf, &second);
9935                 }
9936             }
9937         }
9938
9939         if (appData.icsActive) {
9940             if (appData.quietPlay &&
9941                 (gameMode == IcsPlayingWhite ||
9942                  gameMode == IcsPlayingBlack)) {
9943                 SendToICS(ics_prefix);
9944                 SendToICS("set shout 1\n");
9945             }
9946             nextGameMode = IcsIdle;
9947             ics_user_moved = FALSE;
9948             /* clean up premove.  It's ugly when the game has ended and the
9949              * premove highlights are still on the board.
9950              */
9951             if (gotPremove) {
9952               gotPremove = FALSE;
9953               ClearPremoveHighlights();
9954               DrawPosition(FALSE, boards[currentMove]);
9955             }
9956             if (whosays == GE_ICS) {
9957                 switch (result) {
9958                 case WhiteWins:
9959                     if (gameMode == IcsPlayingWhite)
9960                         PlayIcsWinSound();
9961                     else if(gameMode == IcsPlayingBlack)
9962                         PlayIcsLossSound();
9963                     break;
9964                 case BlackWins:
9965                     if (gameMode == IcsPlayingBlack)
9966                         PlayIcsWinSound();
9967                     else if(gameMode == IcsPlayingWhite)
9968                         PlayIcsLossSound();
9969                     break;
9970                 case GameIsDrawn:
9971                     PlayIcsDrawSound();
9972                     break;
9973                 default:
9974                     PlayIcsUnfinishedSound();
9975                 }
9976             }
9977         } else if (gameMode == EditGame ||
9978                    gameMode == PlayFromGameFile ||
9979                    gameMode == AnalyzeMode ||
9980                    gameMode == AnalyzeFile) {
9981             nextGameMode = gameMode;
9982         } else {
9983             nextGameMode = EndOfGame;
9984         }
9985         pausing = FALSE;
9986         ModeHighlight();
9987     } else {
9988         nextGameMode = gameMode;
9989     }
9990
9991     if (appData.noChessProgram) {
9992         gameMode = nextGameMode;
9993         ModeHighlight();
9994         endingGame = 0; /* [HGM] crash */
9995         return;
9996     }
9997
9998     if (first.reuse) {
9999         /* Put first chess program into idle state */
10000         if (first.pr != NoProc &&
10001             (gameMode == MachinePlaysWhite ||
10002              gameMode == MachinePlaysBlack ||
10003              gameMode == TwoMachinesPlay ||
10004              gameMode == IcsPlayingWhite ||
10005              gameMode == IcsPlayingBlack ||
10006              gameMode == BeginningOfGame)) {
10007             SendToProgram("force\n", &first);
10008             if (first.usePing) {
10009               char buf[MSG_SIZ];
10010               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10011               SendToProgram(buf, &first);
10012             }
10013         }
10014     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10015         /* Kill off first chess program */
10016         if (first.isr != NULL)
10017           RemoveInputSource(first.isr);
10018         first.isr = NULL;
10019
10020         if (first.pr != NoProc) {
10021             ExitAnalyzeMode();
10022             DoSleep( appData.delayBeforeQuit );
10023             SendToProgram("quit\n", &first);
10024             DoSleep( appData.delayAfterQuit );
10025             DestroyChildProcess(first.pr, first.useSigterm);
10026         }
10027         first.pr = NoProc;
10028     }
10029     if (second.reuse) {
10030         /* Put second chess program into idle state */
10031         if (second.pr != NoProc &&
10032             gameMode == TwoMachinesPlay) {
10033             SendToProgram("force\n", &second);
10034             if (second.usePing) {
10035               char buf[MSG_SIZ];
10036               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10037               SendToProgram(buf, &second);
10038             }
10039         }
10040     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10041         /* Kill off second chess program */
10042         if (second.isr != NULL)
10043           RemoveInputSource(second.isr);
10044         second.isr = NULL;
10045
10046         if (second.pr != NoProc) {
10047             DoSleep( appData.delayBeforeQuit );
10048             SendToProgram("quit\n", &second);
10049             DoSleep( appData.delayAfterQuit );
10050             DestroyChildProcess(second.pr, second.useSigterm);
10051         }
10052         second.pr = NoProc;
10053     }
10054
10055     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10056         char resChar = '=';
10057         switch (result) {
10058         case WhiteWins:
10059           resChar = '+';
10060           if (first.twoMachinesColor[0] == 'w') {
10061             first.matchWins++;
10062           } else {
10063             second.matchWins++;
10064           }
10065           break;
10066         case BlackWins:
10067           resChar = '-';
10068           if (first.twoMachinesColor[0] == 'b') {
10069             first.matchWins++;
10070           } else {
10071             second.matchWins++;
10072           }
10073           break;
10074         case GameUnfinished:
10075           resChar = ' ';
10076         default:
10077           break;
10078         }
10079
10080         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10081         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10082             ReserveGame(nextGame, resChar); // sets nextGame
10083             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10084         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10085
10086         if (nextGame <= appData.matchGames && !abortMatch) {
10087             gameMode = nextGameMode;
10088             matchGame = nextGame; // this will be overruled in tourney mode!
10089             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10090             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10091             endingGame = 0; /* [HGM] crash */
10092             return;
10093         } else {
10094             gameMode = nextGameMode;
10095             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10096                      first.tidy, second.tidy,
10097                      first.matchWins, second.matchWins,
10098                      appData.matchGames - (first.matchWins + second.matchWins));
10099             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10100             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10101                 first.twoMachinesColor = "black\n";
10102                 second.twoMachinesColor = "white\n";
10103             } else {
10104                 first.twoMachinesColor = "white\n";
10105                 second.twoMachinesColor = "black\n";
10106             }
10107         }
10108     }
10109     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10110         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10111       ExitAnalyzeMode();
10112     gameMode = nextGameMode;
10113     ModeHighlight();
10114     endingGame = 0;  /* [HGM] crash */
10115     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10116         if(matchMode == TRUE) { // match through command line: exit with or without popup
10117             if(ranking) {
10118                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10119                 else ExitEvent(0);
10120             } else DisplayFatalError(buf, 0, 0);
10121         } else { // match through menu; just stop, with or without popup
10122             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10123             if(ranking){
10124                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10125             } else DisplayNote(buf);
10126       }
10127       if(ranking) free(ranking);
10128     }
10129 }
10130
10131 /* Assumes program was just initialized (initString sent).
10132    Leaves program in force mode. */
10133 void
10134 FeedMovesToProgram(cps, upto)
10135      ChessProgramState *cps;
10136      int upto;
10137 {
10138     int i;
10139
10140     if (appData.debugMode)
10141       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10142               startedFromSetupPosition ? "position and " : "",
10143               backwardMostMove, upto, cps->which);
10144     if(currentlyInitializedVariant != gameInfo.variant) {
10145       char buf[MSG_SIZ];
10146         // [HGM] variantswitch: make engine aware of new variant
10147         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10148                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10149         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10150         SendToProgram(buf, cps);
10151         currentlyInitializedVariant = gameInfo.variant;
10152     }
10153     SendToProgram("force\n", cps);
10154     if (startedFromSetupPosition) {
10155         SendBoard(cps, backwardMostMove);
10156     if (appData.debugMode) {
10157         fprintf(debugFP, "feedMoves\n");
10158     }
10159     }
10160     for (i = backwardMostMove; i < upto; i++) {
10161         SendMoveToProgram(i, cps);
10162     }
10163 }
10164
10165
10166 int
10167 ResurrectChessProgram()
10168 {
10169      /* The chess program may have exited.
10170         If so, restart it and feed it all the moves made so far. */
10171     static int doInit = 0;
10172
10173     if (appData.noChessProgram) return 1;
10174
10175     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10176         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10177         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10178         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10179     } else {
10180         if (first.pr != NoProc) return 1;
10181         StartChessProgram(&first);
10182     }
10183     InitChessProgram(&first, FALSE);
10184     FeedMovesToProgram(&first, currentMove);
10185
10186     if (!first.sendTime) {
10187         /* can't tell gnuchess what its clock should read,
10188            so we bow to its notion. */
10189         ResetClocks();
10190         timeRemaining[0][currentMove] = whiteTimeRemaining;
10191         timeRemaining[1][currentMove] = blackTimeRemaining;
10192     }
10193
10194     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10195                 appData.icsEngineAnalyze) && first.analysisSupport) {
10196       SendToProgram("analyze\n", &first);
10197       first.analyzing = TRUE;
10198     }
10199     return 1;
10200 }
10201
10202 /*
10203  * Button procedures
10204  */
10205 void
10206 Reset(redraw, init)
10207      int redraw, init;
10208 {
10209     int i;
10210
10211     if (appData.debugMode) {
10212         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10213                 redraw, init, gameMode);
10214     }
10215     CleanupTail(); // [HGM] vari: delete any stored variations
10216     pausing = pauseExamInvalid = FALSE;
10217     startedFromSetupPosition = blackPlaysFirst = FALSE;
10218     firstMove = TRUE;
10219     whiteFlag = blackFlag = FALSE;
10220     userOfferedDraw = FALSE;
10221     hintRequested = bookRequested = FALSE;
10222     first.maybeThinking = FALSE;
10223     second.maybeThinking = FALSE;
10224     first.bookSuspend = FALSE; // [HGM] book
10225     second.bookSuspend = FALSE;
10226     thinkOutput[0] = NULLCHAR;
10227     lastHint[0] = NULLCHAR;
10228     ClearGameInfo(&gameInfo);
10229     gameInfo.variant = StringToVariant(appData.variant);
10230     ics_user_moved = ics_clock_paused = FALSE;
10231     ics_getting_history = H_FALSE;
10232     ics_gamenum = -1;
10233     white_holding[0] = black_holding[0] = NULLCHAR;
10234     ClearProgramStats();
10235     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10236
10237     ResetFrontEnd();
10238     ClearHighlights();
10239     flipView = appData.flipView;
10240     ClearPremoveHighlights();
10241     gotPremove = FALSE;
10242     alarmSounded = FALSE;
10243
10244     GameEnds(EndOfFile, NULL, GE_PLAYER);
10245     if(appData.serverMovesName != NULL) {
10246         /* [HGM] prepare to make moves file for broadcasting */
10247         clock_t t = clock();
10248         if(serverMoves != NULL) fclose(serverMoves);
10249         serverMoves = fopen(appData.serverMovesName, "r");
10250         if(serverMoves != NULL) {
10251             fclose(serverMoves);
10252             /* delay 15 sec before overwriting, so all clients can see end */
10253             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10254         }
10255         serverMoves = fopen(appData.serverMovesName, "w");
10256     }
10257
10258     ExitAnalyzeMode();
10259     gameMode = BeginningOfGame;
10260     ModeHighlight();
10261     if(appData.icsActive) gameInfo.variant = VariantNormal;
10262     currentMove = forwardMostMove = backwardMostMove = 0;
10263     InitPosition(redraw);
10264     for (i = 0; i < MAX_MOVES; i++) {
10265         if (commentList[i] != NULL) {
10266             free(commentList[i]);
10267             commentList[i] = NULL;
10268         }
10269     }
10270     ResetClocks();
10271     timeRemaining[0][0] = whiteTimeRemaining;
10272     timeRemaining[1][0] = blackTimeRemaining;
10273
10274     if (first.pr == NULL) {
10275         StartChessProgram(&first);
10276     }
10277     if (init) {
10278             InitChessProgram(&first, startedFromSetupPosition);
10279     }
10280     DisplayTitle("");
10281     DisplayMessage("", "");
10282     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10283     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10284 }
10285
10286 void
10287 AutoPlayGameLoop()
10288 {
10289     for (;;) {
10290         if (!AutoPlayOneMove())
10291           return;
10292         if (matchMode || appData.timeDelay == 0)
10293           continue;
10294         if (appData.timeDelay < 0)
10295           return;
10296         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10297         break;
10298     }
10299 }
10300
10301
10302 int
10303 AutoPlayOneMove()
10304 {
10305     int fromX, fromY, toX, toY;
10306
10307     if (appData.debugMode) {
10308       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10309     }
10310
10311     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10312       return FALSE;
10313
10314     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10315       pvInfoList[currentMove].depth = programStats.depth;
10316       pvInfoList[currentMove].score = programStats.score;
10317       pvInfoList[currentMove].time  = 0;
10318       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10319     }
10320
10321     if (currentMove >= forwardMostMove) {
10322       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10323       gameMode = EditGame;
10324       ModeHighlight();
10325
10326       /* [AS] Clear current move marker at the end of a game */
10327       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10328
10329       return FALSE;
10330     }
10331
10332     toX = moveList[currentMove][2] - AAA;
10333     toY = moveList[currentMove][3] - ONE;
10334
10335     if (moveList[currentMove][1] == '@') {
10336         if (appData.highlightLastMove) {
10337             SetHighlights(-1, -1, toX, toY);
10338         }
10339     } else {
10340         fromX = moveList[currentMove][0] - AAA;
10341         fromY = moveList[currentMove][1] - ONE;
10342
10343         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10344
10345         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10346
10347         if (appData.highlightLastMove) {
10348             SetHighlights(fromX, fromY, toX, toY);
10349         }
10350     }
10351     DisplayMove(currentMove);
10352     SendMoveToProgram(currentMove++, &first);
10353     DisplayBothClocks();
10354     DrawPosition(FALSE, boards[currentMove]);
10355     // [HGM] PV info: always display, routine tests if empty
10356     DisplayComment(currentMove - 1, commentList[currentMove]);
10357     return TRUE;
10358 }
10359
10360
10361 int
10362 LoadGameOneMove(readAhead)
10363      ChessMove readAhead;
10364 {
10365     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10366     char promoChar = NULLCHAR;
10367     ChessMove moveType;
10368     char move[MSG_SIZ];
10369     char *p, *q;
10370
10371     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10372         gameMode != AnalyzeMode && gameMode != Training) {
10373         gameFileFP = NULL;
10374         return FALSE;
10375     }
10376
10377     yyboardindex = forwardMostMove;
10378     if (readAhead != EndOfFile) {
10379       moveType = readAhead;
10380     } else {
10381       if (gameFileFP == NULL)
10382           return FALSE;
10383       moveType = (ChessMove) Myylex();
10384     }
10385
10386     done = FALSE;
10387     switch (moveType) {
10388       case Comment:
10389         if (appData.debugMode)
10390           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10391         p = yy_text;
10392
10393         /* append the comment but don't display it */
10394         AppendComment(currentMove, p, FALSE);
10395         return TRUE;
10396
10397       case WhiteCapturesEnPassant:
10398       case BlackCapturesEnPassant:
10399       case WhitePromotion:
10400       case BlackPromotion:
10401       case WhiteNonPromotion:
10402       case BlackNonPromotion:
10403       case NormalMove:
10404       case WhiteKingSideCastle:
10405       case WhiteQueenSideCastle:
10406       case BlackKingSideCastle:
10407       case BlackQueenSideCastle:
10408       case WhiteKingSideCastleWild:
10409       case WhiteQueenSideCastleWild:
10410       case BlackKingSideCastleWild:
10411       case BlackQueenSideCastleWild:
10412       /* PUSH Fabien */
10413       case WhiteHSideCastleFR:
10414       case WhiteASideCastleFR:
10415       case BlackHSideCastleFR:
10416       case BlackASideCastleFR:
10417       /* POP Fabien */
10418         if (appData.debugMode)
10419           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10420         fromX = currentMoveString[0] - AAA;
10421         fromY = currentMoveString[1] - ONE;
10422         toX = currentMoveString[2] - AAA;
10423         toY = currentMoveString[3] - ONE;
10424         promoChar = currentMoveString[4];
10425         break;
10426
10427       case WhiteDrop:
10428       case BlackDrop:
10429         if (appData.debugMode)
10430           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10431         fromX = moveType == WhiteDrop ?
10432           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10433         (int) CharToPiece(ToLower(currentMoveString[0]));
10434         fromY = DROP_RANK;
10435         toX = currentMoveString[2] - AAA;
10436         toY = currentMoveString[3] - ONE;
10437         break;
10438
10439       case WhiteWins:
10440       case BlackWins:
10441       case GameIsDrawn:
10442       case GameUnfinished:
10443         if (appData.debugMode)
10444           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10445         p = strchr(yy_text, '{');
10446         if (p == NULL) p = strchr(yy_text, '(');
10447         if (p == NULL) {
10448             p = yy_text;
10449             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10450         } else {
10451             q = strchr(p, *p == '{' ? '}' : ')');
10452             if (q != NULL) *q = NULLCHAR;
10453             p++;
10454         }
10455         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10456         GameEnds(moveType, p, GE_FILE);
10457         done = TRUE;
10458         if (cmailMsgLoaded) {
10459             ClearHighlights();
10460             flipView = WhiteOnMove(currentMove);
10461             if (moveType == GameUnfinished) flipView = !flipView;
10462             if (appData.debugMode)
10463               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10464         }
10465         break;
10466
10467       case EndOfFile:
10468         if (appData.debugMode)
10469           fprintf(debugFP, "Parser hit end of file\n");
10470         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10471           case MT_NONE:
10472           case MT_CHECK:
10473             break;
10474           case MT_CHECKMATE:
10475           case MT_STAINMATE:
10476             if (WhiteOnMove(currentMove)) {
10477                 GameEnds(BlackWins, "Black mates", GE_FILE);
10478             } else {
10479                 GameEnds(WhiteWins, "White mates", GE_FILE);
10480             }
10481             break;
10482           case MT_STALEMATE:
10483             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10484             break;
10485         }
10486         done = TRUE;
10487         break;
10488
10489       case MoveNumberOne:
10490         if (lastLoadGameStart == GNUChessGame) {
10491             /* GNUChessGames have numbers, but they aren't move numbers */
10492             if (appData.debugMode)
10493               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10494                       yy_text, (int) moveType);
10495             return LoadGameOneMove(EndOfFile); /* tail recursion */
10496         }
10497         /* else fall thru */
10498
10499       case XBoardGame:
10500       case GNUChessGame:
10501       case PGNTag:
10502         /* Reached start of next game in file */
10503         if (appData.debugMode)
10504           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10505         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10506           case MT_NONE:
10507           case MT_CHECK:
10508             break;
10509           case MT_CHECKMATE:
10510           case MT_STAINMATE:
10511             if (WhiteOnMove(currentMove)) {
10512                 GameEnds(BlackWins, "Black mates", GE_FILE);
10513             } else {
10514                 GameEnds(WhiteWins, "White mates", GE_FILE);
10515             }
10516             break;
10517           case MT_STALEMATE:
10518             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10519             break;
10520         }
10521         done = TRUE;
10522         break;
10523
10524       case PositionDiagram:     /* should not happen; ignore */
10525       case ElapsedTime:         /* ignore */
10526       case NAG:                 /* ignore */
10527         if (appData.debugMode)
10528           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10529                   yy_text, (int) moveType);
10530         return LoadGameOneMove(EndOfFile); /* tail recursion */
10531
10532       case IllegalMove:
10533         if (appData.testLegality) {
10534             if (appData.debugMode)
10535               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10536             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10537                     (forwardMostMove / 2) + 1,
10538                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10539             DisplayError(move, 0);
10540             done = TRUE;
10541         } else {
10542             if (appData.debugMode)
10543               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10544                       yy_text, currentMoveString);
10545             fromX = currentMoveString[0] - AAA;
10546             fromY = currentMoveString[1] - ONE;
10547             toX = currentMoveString[2] - AAA;
10548             toY = currentMoveString[3] - ONE;
10549             promoChar = currentMoveString[4];
10550         }
10551         break;
10552
10553       case AmbiguousMove:
10554         if (appData.debugMode)
10555           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10556         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10557                 (forwardMostMove / 2) + 1,
10558                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10559         DisplayError(move, 0);
10560         done = TRUE;
10561         break;
10562
10563       default:
10564       case ImpossibleMove:
10565         if (appData.debugMode)
10566           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10567         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10568                 (forwardMostMove / 2) + 1,
10569                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10570         DisplayError(move, 0);
10571         done = TRUE;
10572         break;
10573     }
10574
10575     if (done) {
10576         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10577             DrawPosition(FALSE, boards[currentMove]);
10578             DisplayBothClocks();
10579             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10580               DisplayComment(currentMove - 1, commentList[currentMove]);
10581         }
10582         (void) StopLoadGameTimer();
10583         gameFileFP = NULL;
10584         cmailOldMove = forwardMostMove;
10585         return FALSE;
10586     } else {
10587         /* currentMoveString is set as a side-effect of yylex */
10588
10589         thinkOutput[0] = NULLCHAR;
10590         MakeMove(fromX, fromY, toX, toY, promoChar);
10591         currentMove = forwardMostMove;
10592         return TRUE;
10593     }
10594 }
10595
10596 /* Load the nth game from the given file */
10597 int
10598 LoadGameFromFile(filename, n, title, useList)
10599      char *filename;
10600      int n;
10601      char *title;
10602      /*Boolean*/ int useList;
10603 {
10604     FILE *f;
10605     char buf[MSG_SIZ];
10606
10607     if (strcmp(filename, "-") == 0) {
10608         f = stdin;
10609         title = "stdin";
10610     } else {
10611         f = fopen(filename, "rb");
10612         if (f == NULL) {
10613           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10614             DisplayError(buf, errno);
10615             return FALSE;
10616         }
10617     }
10618     if (fseek(f, 0, 0) == -1) {
10619         /* f is not seekable; probably a pipe */
10620         useList = FALSE;
10621     }
10622     if (useList && n == 0) {
10623         int error = GameListBuild(f);
10624         if (error) {
10625             DisplayError(_("Cannot build game list"), error);
10626         } else if (!ListEmpty(&gameList) &&
10627                    ((ListGame *) gameList.tailPred)->number > 1) {
10628             GameListPopUp(f, title);
10629             return TRUE;
10630         }
10631         GameListDestroy();
10632         n = 1;
10633     }
10634     if (n == 0) n = 1;
10635     return LoadGame(f, n, title, FALSE);
10636 }
10637
10638
10639 void
10640 MakeRegisteredMove()
10641 {
10642     int fromX, fromY, toX, toY;
10643     char promoChar;
10644     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10645         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10646           case CMAIL_MOVE:
10647           case CMAIL_DRAW:
10648             if (appData.debugMode)
10649               fprintf(debugFP, "Restoring %s for game %d\n",
10650                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10651
10652             thinkOutput[0] = NULLCHAR;
10653             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10654             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10655             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10656             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10657             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10658             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10659             MakeMove(fromX, fromY, toX, toY, promoChar);
10660             ShowMove(fromX, fromY, toX, toY);
10661
10662             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10663               case MT_NONE:
10664               case MT_CHECK:
10665                 break;
10666
10667               case MT_CHECKMATE:
10668               case MT_STAINMATE:
10669                 if (WhiteOnMove(currentMove)) {
10670                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10671                 } else {
10672                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10673                 }
10674                 break;
10675
10676               case MT_STALEMATE:
10677                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10678                 break;
10679             }
10680
10681             break;
10682
10683           case CMAIL_RESIGN:
10684             if (WhiteOnMove(currentMove)) {
10685                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10686             } else {
10687                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10688             }
10689             break;
10690
10691           case CMAIL_ACCEPT:
10692             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10693             break;
10694
10695           default:
10696             break;
10697         }
10698     }
10699
10700     return;
10701 }
10702
10703 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10704 int
10705 CmailLoadGame(f, gameNumber, title, useList)
10706      FILE *f;
10707      int gameNumber;
10708      char *title;
10709      int useList;
10710 {
10711     int retVal;
10712
10713     if (gameNumber > nCmailGames) {
10714         DisplayError(_("No more games in this message"), 0);
10715         return FALSE;
10716     }
10717     if (f == lastLoadGameFP) {
10718         int offset = gameNumber - lastLoadGameNumber;
10719         if (offset == 0) {
10720             cmailMsg[0] = NULLCHAR;
10721             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10722                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10723                 nCmailMovesRegistered--;
10724             }
10725             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10726             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10727                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10728             }
10729         } else {
10730             if (! RegisterMove()) return FALSE;
10731         }
10732     }
10733
10734     retVal = LoadGame(f, gameNumber, title, useList);
10735
10736     /* Make move registered during previous look at this game, if any */
10737     MakeRegisteredMove();
10738
10739     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10740         commentList[currentMove]
10741           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10742         DisplayComment(currentMove - 1, commentList[currentMove]);
10743     }
10744
10745     return retVal;
10746 }
10747
10748 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10749 int
10750 ReloadGame(offset)
10751      int offset;
10752 {
10753     int gameNumber = lastLoadGameNumber + offset;
10754     if (lastLoadGameFP == NULL) {
10755         DisplayError(_("No game has been loaded yet"), 0);
10756         return FALSE;
10757     }
10758     if (gameNumber <= 0) {
10759         DisplayError(_("Can't back up any further"), 0);
10760         return FALSE;
10761     }
10762     if (cmailMsgLoaded) {
10763         return CmailLoadGame(lastLoadGameFP, gameNumber,
10764                              lastLoadGameTitle, lastLoadGameUseList);
10765     } else {
10766         return LoadGame(lastLoadGameFP, gameNumber,
10767                         lastLoadGameTitle, lastLoadGameUseList);
10768     }
10769 }
10770
10771
10772
10773 /* Load the nth game from open file f */
10774 int
10775 LoadGame(f, gameNumber, title, useList)
10776      FILE *f;
10777      int gameNumber;
10778      char *title;
10779      int useList;
10780 {
10781     ChessMove cm;
10782     char buf[MSG_SIZ];
10783     int gn = gameNumber;
10784     ListGame *lg = NULL;
10785     int numPGNTags = 0;
10786     int err;
10787     GameMode oldGameMode;
10788     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10789
10790     if (appData.debugMode)
10791         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10792
10793     if (gameMode == Training )
10794         SetTrainingModeOff();
10795
10796     oldGameMode = gameMode;
10797     if (gameMode != BeginningOfGame) {
10798       Reset(FALSE, TRUE);
10799     }
10800
10801     gameFileFP = f;
10802     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10803         fclose(lastLoadGameFP);
10804     }
10805
10806     if (useList) {
10807         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10808
10809         if (lg) {
10810             fseek(f, lg->offset, 0);
10811             GameListHighlight(gameNumber);
10812             gn = 1;
10813         }
10814         else {
10815             DisplayError(_("Game number out of range"), 0);
10816             return FALSE;
10817         }
10818     } else {
10819         GameListDestroy();
10820         if (fseek(f, 0, 0) == -1) {
10821             if (f == lastLoadGameFP ?
10822                 gameNumber == lastLoadGameNumber + 1 :
10823                 gameNumber == 1) {
10824                 gn = 1;
10825             } else {
10826                 DisplayError(_("Can't seek on game file"), 0);
10827                 return FALSE;
10828             }
10829         }
10830     }
10831     lastLoadGameFP = f;
10832     lastLoadGameNumber = gameNumber;
10833     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10834     lastLoadGameUseList = useList;
10835
10836     yynewfile(f);
10837
10838     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10839       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10840                 lg->gameInfo.black);
10841             DisplayTitle(buf);
10842     } else if (*title != NULLCHAR) {
10843         if (gameNumber > 1) {
10844           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10845             DisplayTitle(buf);
10846         } else {
10847             DisplayTitle(title);
10848         }
10849     }
10850
10851     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10852         gameMode = PlayFromGameFile;
10853         ModeHighlight();
10854     }
10855
10856     currentMove = forwardMostMove = backwardMostMove = 0;
10857     CopyBoard(boards[0], initialPosition);
10858     StopClocks();
10859
10860     /*
10861      * Skip the first gn-1 games in the file.
10862      * Also skip over anything that precedes an identifiable
10863      * start of game marker, to avoid being confused by
10864      * garbage at the start of the file.  Currently
10865      * recognized start of game markers are the move number "1",
10866      * the pattern "gnuchess .* game", the pattern
10867      * "^[#;%] [^ ]* game file", and a PGN tag block.
10868      * A game that starts with one of the latter two patterns
10869      * will also have a move number 1, possibly
10870      * following a position diagram.
10871      * 5-4-02: Let's try being more lenient and allowing a game to
10872      * start with an unnumbered move.  Does that break anything?
10873      */
10874     cm = lastLoadGameStart = EndOfFile;
10875     while (gn > 0) {
10876         yyboardindex = forwardMostMove;
10877         cm = (ChessMove) Myylex();
10878         switch (cm) {
10879           case EndOfFile:
10880             if (cmailMsgLoaded) {
10881                 nCmailGames = CMAIL_MAX_GAMES - gn;
10882             } else {
10883                 Reset(TRUE, TRUE);
10884                 DisplayError(_("Game not found in file"), 0);
10885             }
10886             return FALSE;
10887
10888           case GNUChessGame:
10889           case XBoardGame:
10890             gn--;
10891             lastLoadGameStart = cm;
10892             break;
10893
10894           case MoveNumberOne:
10895             switch (lastLoadGameStart) {
10896               case GNUChessGame:
10897               case XBoardGame:
10898               case PGNTag:
10899                 break;
10900               case MoveNumberOne:
10901               case EndOfFile:
10902                 gn--;           /* count this game */
10903                 lastLoadGameStart = cm;
10904                 break;
10905               default:
10906                 /* impossible */
10907                 break;
10908             }
10909             break;
10910
10911           case PGNTag:
10912             switch (lastLoadGameStart) {
10913               case GNUChessGame:
10914               case PGNTag:
10915               case MoveNumberOne:
10916               case EndOfFile:
10917                 gn--;           /* count this game */
10918                 lastLoadGameStart = cm;
10919                 break;
10920               case XBoardGame:
10921                 lastLoadGameStart = cm; /* game counted already */
10922                 break;
10923               default:
10924                 /* impossible */
10925                 break;
10926             }
10927             if (gn > 0) {
10928                 do {
10929                     yyboardindex = forwardMostMove;
10930                     cm = (ChessMove) Myylex();
10931                 } while (cm == PGNTag || cm == Comment);
10932             }
10933             break;
10934
10935           case WhiteWins:
10936           case BlackWins:
10937           case GameIsDrawn:
10938             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10939                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10940                     != CMAIL_OLD_RESULT) {
10941                     nCmailResults ++ ;
10942                     cmailResult[  CMAIL_MAX_GAMES
10943                                 - gn - 1] = CMAIL_OLD_RESULT;
10944                 }
10945             }
10946             break;
10947
10948           case NormalMove:
10949             /* Only a NormalMove can be at the start of a game
10950              * without a position diagram. */
10951             if (lastLoadGameStart == EndOfFile ) {
10952               gn--;
10953               lastLoadGameStart = MoveNumberOne;
10954             }
10955             break;
10956
10957           default:
10958             break;
10959         }
10960     }
10961
10962     if (appData.debugMode)
10963       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10964
10965     if (cm == XBoardGame) {
10966         /* Skip any header junk before position diagram and/or move 1 */
10967         for (;;) {
10968             yyboardindex = forwardMostMove;
10969             cm = (ChessMove) Myylex();
10970
10971             if (cm == EndOfFile ||
10972                 cm == GNUChessGame || cm == XBoardGame) {
10973                 /* Empty game; pretend end-of-file and handle later */
10974                 cm = EndOfFile;
10975                 break;
10976             }
10977
10978             if (cm == MoveNumberOne || cm == PositionDiagram ||
10979                 cm == PGNTag || cm == Comment)
10980               break;
10981         }
10982     } else if (cm == GNUChessGame) {
10983         if (gameInfo.event != NULL) {
10984             free(gameInfo.event);
10985         }
10986         gameInfo.event = StrSave(yy_text);
10987     }
10988
10989     startedFromSetupPosition = FALSE;
10990     while (cm == PGNTag) {
10991         if (appData.debugMode)
10992           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10993         err = ParsePGNTag(yy_text, &gameInfo);
10994         if (!err) numPGNTags++;
10995
10996         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10997         if(gameInfo.variant != oldVariant) {
10998             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10999             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11000             InitPosition(TRUE);
11001             oldVariant = gameInfo.variant;
11002             if (appData.debugMode)
11003               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11004         }
11005
11006
11007         if (gameInfo.fen != NULL) {
11008           Board initial_position;
11009           startedFromSetupPosition = TRUE;
11010           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11011             Reset(TRUE, TRUE);
11012             DisplayError(_("Bad FEN position in file"), 0);
11013             return FALSE;
11014           }
11015           CopyBoard(boards[0], initial_position);
11016           if (blackPlaysFirst) {
11017             currentMove = forwardMostMove = backwardMostMove = 1;
11018             CopyBoard(boards[1], initial_position);
11019             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11020             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11021             timeRemaining[0][1] = whiteTimeRemaining;
11022             timeRemaining[1][1] = blackTimeRemaining;
11023             if (commentList[0] != NULL) {
11024               commentList[1] = commentList[0];
11025               commentList[0] = NULL;
11026             }
11027           } else {
11028             currentMove = forwardMostMove = backwardMostMove = 0;
11029           }
11030           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11031           {   int i;
11032               initialRulePlies = FENrulePlies;
11033               for( i=0; i< nrCastlingRights; i++ )
11034                   initialRights[i] = initial_position[CASTLING][i];
11035           }
11036           yyboardindex = forwardMostMove;
11037           free(gameInfo.fen);
11038           gameInfo.fen = NULL;
11039         }
11040
11041         yyboardindex = forwardMostMove;
11042         cm = (ChessMove) Myylex();
11043
11044         /* Handle comments interspersed among the tags */
11045         while (cm == Comment) {
11046             char *p;
11047             if (appData.debugMode)
11048               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11049             p = yy_text;
11050             AppendComment(currentMove, p, FALSE);
11051             yyboardindex = forwardMostMove;
11052             cm = (ChessMove) Myylex();
11053         }
11054     }
11055
11056     /* don't rely on existence of Event tag since if game was
11057      * pasted from clipboard the Event tag may not exist
11058      */
11059     if (numPGNTags > 0){
11060         char *tags;
11061         if (gameInfo.variant == VariantNormal) {
11062           VariantClass v = StringToVariant(gameInfo.event);
11063           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11064           if(v < VariantShogi) gameInfo.variant = v;
11065         }
11066         if (!matchMode) {
11067           if( appData.autoDisplayTags ) {
11068             tags = PGNTags(&gameInfo);
11069             TagsPopUp(tags, CmailMsg());
11070             free(tags);
11071           }
11072         }
11073     } else {
11074         /* Make something up, but don't display it now */
11075         SetGameInfo();
11076         TagsPopDown();
11077     }
11078
11079     if (cm == PositionDiagram) {
11080         int i, j;
11081         char *p;
11082         Board initial_position;
11083
11084         if (appData.debugMode)
11085           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11086
11087         if (!startedFromSetupPosition) {
11088             p = yy_text;
11089             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11090               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11091                 switch (*p) {
11092                   case '{':
11093                   case '[':
11094                   case '-':
11095                   case ' ':
11096                   case '\t':
11097                   case '\n':
11098                   case '\r':
11099                     break;
11100                   default:
11101                     initial_position[i][j++] = CharToPiece(*p);
11102                     break;
11103                 }
11104             while (*p == ' ' || *p == '\t' ||
11105                    *p == '\n' || *p == '\r') p++;
11106
11107             if (strncmp(p, "black", strlen("black"))==0)
11108               blackPlaysFirst = TRUE;
11109             else
11110               blackPlaysFirst = FALSE;
11111             startedFromSetupPosition = TRUE;
11112
11113             CopyBoard(boards[0], initial_position);
11114             if (blackPlaysFirst) {
11115                 currentMove = forwardMostMove = backwardMostMove = 1;
11116                 CopyBoard(boards[1], initial_position);
11117                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11118                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11119                 timeRemaining[0][1] = whiteTimeRemaining;
11120                 timeRemaining[1][1] = blackTimeRemaining;
11121                 if (commentList[0] != NULL) {
11122                     commentList[1] = commentList[0];
11123                     commentList[0] = NULL;
11124                 }
11125             } else {
11126                 currentMove = forwardMostMove = backwardMostMove = 0;
11127             }
11128         }
11129         yyboardindex = forwardMostMove;
11130         cm = (ChessMove) Myylex();
11131     }
11132
11133     if (first.pr == NoProc) {
11134         StartChessProgram(&first);
11135     }
11136     InitChessProgram(&first, FALSE);
11137     SendToProgram("force\n", &first);
11138     if (startedFromSetupPosition) {
11139         SendBoard(&first, forwardMostMove);
11140     if (appData.debugMode) {
11141         fprintf(debugFP, "Load Game\n");
11142     }
11143         DisplayBothClocks();
11144     }
11145
11146     /* [HGM] server: flag to write setup moves in broadcast file as one */
11147     loadFlag = appData.suppressLoadMoves;
11148
11149     while (cm == Comment) {
11150         char *p;
11151         if (appData.debugMode)
11152           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11153         p = yy_text;
11154         AppendComment(currentMove, p, FALSE);
11155         yyboardindex = forwardMostMove;
11156         cm = (ChessMove) Myylex();
11157     }
11158
11159     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11160         cm == WhiteWins || cm == BlackWins ||
11161         cm == GameIsDrawn || cm == GameUnfinished) {
11162         DisplayMessage("", _("No moves in game"));
11163         if (cmailMsgLoaded) {
11164             if (appData.debugMode)
11165               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11166             ClearHighlights();
11167             flipView = FALSE;
11168         }
11169         DrawPosition(FALSE, boards[currentMove]);
11170         DisplayBothClocks();
11171         gameMode = EditGame;
11172         ModeHighlight();
11173         gameFileFP = NULL;
11174         cmailOldMove = 0;
11175         return TRUE;
11176     }
11177
11178     // [HGM] PV info: routine tests if comment empty
11179     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11180         DisplayComment(currentMove - 1, commentList[currentMove]);
11181     }
11182     if (!matchMode && appData.timeDelay != 0)
11183       DrawPosition(FALSE, boards[currentMove]);
11184
11185     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11186       programStats.ok_to_send = 1;
11187     }
11188
11189     /* if the first token after the PGN tags is a move
11190      * and not move number 1, retrieve it from the parser
11191      */
11192     if (cm != MoveNumberOne)
11193         LoadGameOneMove(cm);
11194
11195     /* load the remaining moves from the file */
11196     while (LoadGameOneMove(EndOfFile)) {
11197       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11198       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11199     }
11200
11201     /* rewind to the start of the game */
11202     currentMove = backwardMostMove;
11203
11204     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11205
11206     if (oldGameMode == AnalyzeFile ||
11207         oldGameMode == AnalyzeMode) {
11208       AnalyzeFileEvent();
11209     }
11210
11211     if (matchMode || appData.timeDelay == 0) {
11212       ToEndEvent();
11213       gameMode = EditGame;
11214       ModeHighlight();
11215     } else if (appData.timeDelay > 0) {
11216       AutoPlayGameLoop();
11217     }
11218
11219     if (appData.debugMode)
11220         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11221
11222     loadFlag = 0; /* [HGM] true game starts */
11223     return TRUE;
11224 }
11225
11226 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11227 int
11228 ReloadPosition(offset)
11229      int offset;
11230 {
11231     int positionNumber = lastLoadPositionNumber + offset;
11232     if (lastLoadPositionFP == NULL) {
11233         DisplayError(_("No position has been loaded yet"), 0);
11234         return FALSE;
11235     }
11236     if (positionNumber <= 0) {
11237         DisplayError(_("Can't back up any further"), 0);
11238         return FALSE;
11239     }
11240     return LoadPosition(lastLoadPositionFP, positionNumber,
11241                         lastLoadPositionTitle);
11242 }
11243
11244 /* Load the nth position from the given file */
11245 int
11246 LoadPositionFromFile(filename, n, title)
11247      char *filename;
11248      int n;
11249      char *title;
11250 {
11251     FILE *f;
11252     char buf[MSG_SIZ];
11253
11254     if (strcmp(filename, "-") == 0) {
11255         return LoadPosition(stdin, n, "stdin");
11256     } else {
11257         f = fopen(filename, "rb");
11258         if (f == NULL) {
11259             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11260             DisplayError(buf, errno);
11261             return FALSE;
11262         } else {
11263             return LoadPosition(f, n, title);
11264         }
11265     }
11266 }
11267
11268 /* Load the nth position from the given open file, and close it */
11269 int
11270 LoadPosition(f, positionNumber, title)
11271      FILE *f;
11272      int positionNumber;
11273      char *title;
11274 {
11275     char *p, line[MSG_SIZ];
11276     Board initial_position;
11277     int i, j, fenMode, pn;
11278
11279     if (gameMode == Training )
11280         SetTrainingModeOff();
11281
11282     if (gameMode != BeginningOfGame) {
11283         Reset(FALSE, TRUE);
11284     }
11285     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11286         fclose(lastLoadPositionFP);
11287     }
11288     if (positionNumber == 0) positionNumber = 1;
11289     lastLoadPositionFP = f;
11290     lastLoadPositionNumber = positionNumber;
11291     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11292     if (first.pr == NoProc) {
11293       StartChessProgram(&first);
11294       InitChessProgram(&first, FALSE);
11295     }
11296     pn = positionNumber;
11297     if (positionNumber < 0) {
11298         /* Negative position number means to seek to that byte offset */
11299         if (fseek(f, -positionNumber, 0) == -1) {
11300             DisplayError(_("Can't seek on position file"), 0);
11301             return FALSE;
11302         };
11303         pn = 1;
11304     } else {
11305         if (fseek(f, 0, 0) == -1) {
11306             if (f == lastLoadPositionFP ?
11307                 positionNumber == lastLoadPositionNumber + 1 :
11308                 positionNumber == 1) {
11309                 pn = 1;
11310             } else {
11311                 DisplayError(_("Can't seek on position file"), 0);
11312                 return FALSE;
11313             }
11314         }
11315     }
11316     /* See if this file is FEN or old-style xboard */
11317     if (fgets(line, MSG_SIZ, f) == NULL) {
11318         DisplayError(_("Position not found in file"), 0);
11319         return FALSE;
11320     }
11321     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11322     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11323
11324     if (pn >= 2) {
11325         if (fenMode || line[0] == '#') pn--;
11326         while (pn > 0) {
11327             /* skip positions before number pn */
11328             if (fgets(line, MSG_SIZ, f) == NULL) {
11329                 Reset(TRUE, TRUE);
11330                 DisplayError(_("Position not found in file"), 0);
11331                 return FALSE;
11332             }
11333             if (fenMode || line[0] == '#') pn--;
11334         }
11335     }
11336
11337     if (fenMode) {
11338         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11339             DisplayError(_("Bad FEN position in file"), 0);
11340             return FALSE;
11341         }
11342     } else {
11343         (void) fgets(line, MSG_SIZ, f);
11344         (void) fgets(line, MSG_SIZ, f);
11345
11346         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11347             (void) fgets(line, MSG_SIZ, f);
11348             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11349                 if (*p == ' ')
11350                   continue;
11351                 initial_position[i][j++] = CharToPiece(*p);
11352             }
11353         }
11354
11355         blackPlaysFirst = FALSE;
11356         if (!feof(f)) {
11357             (void) fgets(line, MSG_SIZ, f);
11358             if (strncmp(line, "black", strlen("black"))==0)
11359               blackPlaysFirst = TRUE;
11360         }
11361     }
11362     startedFromSetupPosition = TRUE;
11363
11364     SendToProgram("force\n", &first);
11365     CopyBoard(boards[0], initial_position);
11366     if (blackPlaysFirst) {
11367         currentMove = forwardMostMove = backwardMostMove = 1;
11368         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11369         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11370         CopyBoard(boards[1], initial_position);
11371         DisplayMessage("", _("Black to play"));
11372     } else {
11373         currentMove = forwardMostMove = backwardMostMove = 0;
11374         DisplayMessage("", _("White to play"));
11375     }
11376     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11377     SendBoard(&first, forwardMostMove);
11378     if (appData.debugMode) {
11379 int i, j;
11380   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11381   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11382         fprintf(debugFP, "Load Position\n");
11383     }
11384
11385     if (positionNumber > 1) {
11386       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11387         DisplayTitle(line);
11388     } else {
11389         DisplayTitle(title);
11390     }
11391     gameMode = EditGame;
11392     ModeHighlight();
11393     ResetClocks();
11394     timeRemaining[0][1] = whiteTimeRemaining;
11395     timeRemaining[1][1] = blackTimeRemaining;
11396     DrawPosition(FALSE, boards[currentMove]);
11397
11398     return TRUE;
11399 }
11400
11401
11402 void
11403 CopyPlayerNameIntoFileName(dest, src)
11404      char **dest, *src;
11405 {
11406     while (*src != NULLCHAR && *src != ',') {
11407         if (*src == ' ') {
11408             *(*dest)++ = '_';
11409             src++;
11410         } else {
11411             *(*dest)++ = *src++;
11412         }
11413     }
11414 }
11415
11416 char *DefaultFileName(ext)
11417      char *ext;
11418 {
11419     static char def[MSG_SIZ];
11420     char *p;
11421
11422     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11423         p = def;
11424         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11425         *p++ = '-';
11426         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11427         *p++ = '.';
11428         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11429     } else {
11430         def[0] = NULLCHAR;
11431     }
11432     return def;
11433 }
11434
11435 /* Save the current game to the given file */
11436 int
11437 SaveGameToFile(filename, append)
11438      char *filename;
11439      int append;
11440 {
11441     FILE *f;
11442     char buf[MSG_SIZ];
11443     int result;
11444
11445     if (strcmp(filename, "-") == 0) {
11446         return SaveGame(stdout, 0, NULL);
11447     } else {
11448         f = fopen(filename, append ? "a" : "w");
11449         if (f == NULL) {
11450             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11451             DisplayError(buf, errno);
11452             return FALSE;
11453         } else {
11454             safeStrCpy(buf, lastMsg, MSG_SIZ);
11455             DisplayMessage(_("Waiting for access to save file"), "");
11456             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11457             DisplayMessage(_("Saving game"), "");
11458             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11459             result = SaveGame(f, 0, NULL);
11460             DisplayMessage(buf, "");
11461             return result;
11462         }
11463     }
11464 }
11465
11466 char *
11467 SavePart(str)
11468      char *str;
11469 {
11470     static char buf[MSG_SIZ];
11471     char *p;
11472
11473     p = strchr(str, ' ');
11474     if (p == NULL) return str;
11475     strncpy(buf, str, p - str);
11476     buf[p - str] = NULLCHAR;
11477     return buf;
11478 }
11479
11480 #define PGN_MAX_LINE 75
11481
11482 #define PGN_SIDE_WHITE  0
11483 #define PGN_SIDE_BLACK  1
11484
11485 /* [AS] */
11486 static int FindFirstMoveOutOfBook( int side )
11487 {
11488     int result = -1;
11489
11490     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11491         int index = backwardMostMove;
11492         int has_book_hit = 0;
11493
11494         if( (index % 2) != side ) {
11495             index++;
11496         }
11497
11498         while( index < forwardMostMove ) {
11499             /* Check to see if engine is in book */
11500             int depth = pvInfoList[index].depth;
11501             int score = pvInfoList[index].score;
11502             int in_book = 0;
11503
11504             if( depth <= 2 ) {
11505                 in_book = 1;
11506             }
11507             else if( score == 0 && depth == 63 ) {
11508                 in_book = 1; /* Zappa */
11509             }
11510             else if( score == 2 && depth == 99 ) {
11511                 in_book = 1; /* Abrok */
11512             }
11513
11514             has_book_hit += in_book;
11515
11516             if( ! in_book ) {
11517                 result = index;
11518
11519                 break;
11520             }
11521
11522             index += 2;
11523         }
11524     }
11525
11526     return result;
11527 }
11528
11529 /* [AS] */
11530 void GetOutOfBookInfo( char * buf )
11531 {
11532     int oob[2];
11533     int i;
11534     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11535
11536     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11537     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11538
11539     *buf = '\0';
11540
11541     if( oob[0] >= 0 || oob[1] >= 0 ) {
11542         for( i=0; i<2; i++ ) {
11543             int idx = oob[i];
11544
11545             if( idx >= 0 ) {
11546                 if( i > 0 && oob[0] >= 0 ) {
11547                     strcat( buf, "   " );
11548                 }
11549
11550                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11551                 sprintf( buf+strlen(buf), "%s%.2f",
11552                     pvInfoList[idx].score >= 0 ? "+" : "",
11553                     pvInfoList[idx].score / 100.0 );
11554             }
11555         }
11556     }
11557 }
11558
11559 /* Save game in PGN style and close the file */
11560 int
11561 SaveGamePGN(f)
11562      FILE *f;
11563 {
11564     int i, offset, linelen, newblock;
11565     time_t tm;
11566 //    char *movetext;
11567     char numtext[32];
11568     int movelen, numlen, blank;
11569     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11570
11571     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11572
11573     tm = time((time_t *) NULL);
11574
11575     PrintPGNTags(f, &gameInfo);
11576
11577     if (backwardMostMove > 0 || startedFromSetupPosition) {
11578         char *fen = PositionToFEN(backwardMostMove, NULL);
11579         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11580         fprintf(f, "\n{--------------\n");
11581         PrintPosition(f, backwardMostMove);
11582         fprintf(f, "--------------}\n");
11583         free(fen);
11584     }
11585     else {
11586         /* [AS] Out of book annotation */
11587         if( appData.saveOutOfBookInfo ) {
11588             char buf[64];
11589
11590             GetOutOfBookInfo( buf );
11591
11592             if( buf[0] != '\0' ) {
11593                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11594             }
11595         }
11596
11597         fprintf(f, "\n");
11598     }
11599
11600     i = backwardMostMove;
11601     linelen = 0;
11602     newblock = TRUE;
11603
11604     while (i < forwardMostMove) {
11605         /* Print comments preceding this move */
11606         if (commentList[i] != NULL) {
11607             if (linelen > 0) fprintf(f, "\n");
11608             fprintf(f, "%s", commentList[i]);
11609             linelen = 0;
11610             newblock = TRUE;
11611         }
11612
11613         /* Format move number */
11614         if ((i % 2) == 0)
11615           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11616         else
11617           if (newblock)
11618             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11619           else
11620             numtext[0] = NULLCHAR;
11621
11622         numlen = strlen(numtext);
11623         newblock = FALSE;
11624
11625         /* Print move number */
11626         blank = linelen > 0 && numlen > 0;
11627         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11628             fprintf(f, "\n");
11629             linelen = 0;
11630             blank = 0;
11631         }
11632         if (blank) {
11633             fprintf(f, " ");
11634             linelen++;
11635         }
11636         fprintf(f, "%s", numtext);
11637         linelen += numlen;
11638
11639         /* Get move */
11640         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11641         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11642
11643         /* Print move */
11644         blank = linelen > 0 && movelen > 0;
11645         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11646             fprintf(f, "\n");
11647             linelen = 0;
11648             blank = 0;
11649         }
11650         if (blank) {
11651             fprintf(f, " ");
11652             linelen++;
11653         }
11654         fprintf(f, "%s", move_buffer);
11655         linelen += movelen;
11656
11657         /* [AS] Add PV info if present */
11658         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11659             /* [HGM] add time */
11660             char buf[MSG_SIZ]; int seconds;
11661
11662             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11663
11664             if( seconds <= 0)
11665               buf[0] = 0;
11666             else
11667               if( seconds < 30 )
11668                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11669               else
11670                 {
11671                   seconds = (seconds + 4)/10; // round to full seconds
11672                   if( seconds < 60 )
11673                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11674                   else
11675                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11676                 }
11677
11678             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11679                       pvInfoList[i].score >= 0 ? "+" : "",
11680                       pvInfoList[i].score / 100.0,
11681                       pvInfoList[i].depth,
11682                       buf );
11683
11684             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11685
11686             /* Print score/depth */
11687             blank = linelen > 0 && movelen > 0;
11688             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11689                 fprintf(f, "\n");
11690                 linelen = 0;
11691                 blank = 0;
11692             }
11693             if (blank) {
11694                 fprintf(f, " ");
11695                 linelen++;
11696             }
11697             fprintf(f, "%s", move_buffer);
11698             linelen += movelen;
11699         }
11700
11701         i++;
11702     }
11703
11704     /* Start a new line */
11705     if (linelen > 0) fprintf(f, "\n");
11706
11707     /* Print comments after last move */
11708     if (commentList[i] != NULL) {
11709         fprintf(f, "%s\n", commentList[i]);
11710     }
11711
11712     /* Print result */
11713     if (gameInfo.resultDetails != NULL &&
11714         gameInfo.resultDetails[0] != NULLCHAR) {
11715         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11716                 PGNResult(gameInfo.result));
11717     } else {
11718         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11719     }
11720
11721     fclose(f);
11722     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11723     return TRUE;
11724 }
11725
11726 /* Save game in old style and close the file */
11727 int
11728 SaveGameOldStyle(f)
11729      FILE *f;
11730 {
11731     int i, offset;
11732     time_t tm;
11733
11734     tm = time((time_t *) NULL);
11735
11736     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11737     PrintOpponents(f);
11738
11739     if (backwardMostMove > 0 || startedFromSetupPosition) {
11740         fprintf(f, "\n[--------------\n");
11741         PrintPosition(f, backwardMostMove);
11742         fprintf(f, "--------------]\n");
11743     } else {
11744         fprintf(f, "\n");
11745     }
11746
11747     i = backwardMostMove;
11748     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11749
11750     while (i < forwardMostMove) {
11751         if (commentList[i] != NULL) {
11752             fprintf(f, "[%s]\n", commentList[i]);
11753         }
11754
11755         if ((i % 2) == 1) {
11756             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11757             i++;
11758         } else {
11759             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11760             i++;
11761             if (commentList[i] != NULL) {
11762                 fprintf(f, "\n");
11763                 continue;
11764             }
11765             if (i >= forwardMostMove) {
11766                 fprintf(f, "\n");
11767                 break;
11768             }
11769             fprintf(f, "%s\n", parseList[i]);
11770             i++;
11771         }
11772     }
11773
11774     if (commentList[i] != NULL) {
11775         fprintf(f, "[%s]\n", commentList[i]);
11776     }
11777
11778     /* This isn't really the old style, but it's close enough */
11779     if (gameInfo.resultDetails != NULL &&
11780         gameInfo.resultDetails[0] != NULLCHAR) {
11781         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11782                 gameInfo.resultDetails);
11783     } else {
11784         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11785     }
11786
11787     fclose(f);
11788     return TRUE;
11789 }
11790
11791 /* Save the current game to open file f and close the file */
11792 int
11793 SaveGame(f, dummy, dummy2)
11794      FILE *f;
11795      int dummy;
11796      char *dummy2;
11797 {
11798     if (gameMode == EditPosition) EditPositionDone(TRUE);
11799     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11800     if (appData.oldSaveStyle)
11801       return SaveGameOldStyle(f);
11802     else
11803       return SaveGamePGN(f);
11804 }
11805
11806 /* Save the current position to the given file */
11807 int
11808 SavePositionToFile(filename)
11809      char *filename;
11810 {
11811     FILE *f;
11812     char buf[MSG_SIZ];
11813
11814     if (strcmp(filename, "-") == 0) {
11815         return SavePosition(stdout, 0, NULL);
11816     } else {
11817         f = fopen(filename, "a");
11818         if (f == NULL) {
11819             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11820             DisplayError(buf, errno);
11821             return FALSE;
11822         } else {
11823             safeStrCpy(buf, lastMsg, MSG_SIZ);
11824             DisplayMessage(_("Waiting for access to save file"), "");
11825             flock(fileno(f), LOCK_EX); // [HGM] lock
11826             DisplayMessage(_("Saving position"), "");
11827             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11828             SavePosition(f, 0, NULL);
11829             DisplayMessage(buf, "");
11830             return TRUE;
11831         }
11832     }
11833 }
11834
11835 /* Save the current position to the given open file and close the file */
11836 int
11837 SavePosition(f, dummy, dummy2)
11838      FILE *f;
11839      int dummy;
11840      char *dummy2;
11841 {
11842     time_t tm;
11843     char *fen;
11844
11845     if (gameMode == EditPosition) EditPositionDone(TRUE);
11846     if (appData.oldSaveStyle) {
11847         tm = time((time_t *) NULL);
11848
11849         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11850         PrintOpponents(f);
11851         fprintf(f, "[--------------\n");
11852         PrintPosition(f, currentMove);
11853         fprintf(f, "--------------]\n");
11854     } else {
11855         fen = PositionToFEN(currentMove, NULL);
11856         fprintf(f, "%s\n", fen);
11857         free(fen);
11858     }
11859     fclose(f);
11860     return TRUE;
11861 }
11862
11863 void
11864 ReloadCmailMsgEvent(unregister)
11865      int unregister;
11866 {
11867 #if !WIN32
11868     static char *inFilename = NULL;
11869     static char *outFilename;
11870     int i;
11871     struct stat inbuf, outbuf;
11872     int status;
11873
11874     /* Any registered moves are unregistered if unregister is set, */
11875     /* i.e. invoked by the signal handler */
11876     if (unregister) {
11877         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11878             cmailMoveRegistered[i] = FALSE;
11879             if (cmailCommentList[i] != NULL) {
11880                 free(cmailCommentList[i]);
11881                 cmailCommentList[i] = NULL;
11882             }
11883         }
11884         nCmailMovesRegistered = 0;
11885     }
11886
11887     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11888         cmailResult[i] = CMAIL_NOT_RESULT;
11889     }
11890     nCmailResults = 0;
11891
11892     if (inFilename == NULL) {
11893         /* Because the filenames are static they only get malloced once  */
11894         /* and they never get freed                                      */
11895         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11896         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11897
11898         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11899         sprintf(outFilename, "%s.out", appData.cmailGameName);
11900     }
11901
11902     status = stat(outFilename, &outbuf);
11903     if (status < 0) {
11904         cmailMailedMove = FALSE;
11905     } else {
11906         status = stat(inFilename, &inbuf);
11907         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11908     }
11909
11910     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11911        counts the games, notes how each one terminated, etc.
11912
11913        It would be nice to remove this kludge and instead gather all
11914        the information while building the game list.  (And to keep it
11915        in the game list nodes instead of having a bunch of fixed-size
11916        parallel arrays.)  Note this will require getting each game's
11917        termination from the PGN tags, as the game list builder does
11918        not process the game moves.  --mann
11919        */
11920     cmailMsgLoaded = TRUE;
11921     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11922
11923     /* Load first game in the file or popup game menu */
11924     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11925
11926 #endif /* !WIN32 */
11927     return;
11928 }
11929
11930 int
11931 RegisterMove()
11932 {
11933     FILE *f;
11934     char string[MSG_SIZ];
11935
11936     if (   cmailMailedMove
11937         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11938         return TRUE;            /* Allow free viewing  */
11939     }
11940
11941     /* Unregister move to ensure that we don't leave RegisterMove        */
11942     /* with the move registered when the conditions for registering no   */
11943     /* longer hold                                                       */
11944     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11945         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11946         nCmailMovesRegistered --;
11947
11948         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11949           {
11950               free(cmailCommentList[lastLoadGameNumber - 1]);
11951               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11952           }
11953     }
11954
11955     if (cmailOldMove == -1) {
11956         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11957         return FALSE;
11958     }
11959
11960     if (currentMove > cmailOldMove + 1) {
11961         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11962         return FALSE;
11963     }
11964
11965     if (currentMove < cmailOldMove) {
11966         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11967         return FALSE;
11968     }
11969
11970     if (forwardMostMove > currentMove) {
11971         /* Silently truncate extra moves */
11972         TruncateGame();
11973     }
11974
11975     if (   (currentMove == cmailOldMove + 1)
11976         || (   (currentMove == cmailOldMove)
11977             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11978                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11979         if (gameInfo.result != GameUnfinished) {
11980             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11981         }
11982
11983         if (commentList[currentMove] != NULL) {
11984             cmailCommentList[lastLoadGameNumber - 1]
11985               = StrSave(commentList[currentMove]);
11986         }
11987         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11988
11989         if (appData.debugMode)
11990           fprintf(debugFP, "Saving %s for game %d\n",
11991                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11992
11993         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11994
11995         f = fopen(string, "w");
11996         if (appData.oldSaveStyle) {
11997             SaveGameOldStyle(f); /* also closes the file */
11998
11999             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12000             f = fopen(string, "w");
12001             SavePosition(f, 0, NULL); /* also closes the file */
12002         } else {
12003             fprintf(f, "{--------------\n");
12004             PrintPosition(f, currentMove);
12005             fprintf(f, "--------------}\n\n");
12006
12007             SaveGame(f, 0, NULL); /* also closes the file*/
12008         }
12009
12010         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12011         nCmailMovesRegistered ++;
12012     } else if (nCmailGames == 1) {
12013         DisplayError(_("You have not made a move yet"), 0);
12014         return FALSE;
12015     }
12016
12017     return TRUE;
12018 }
12019
12020 void
12021 MailMoveEvent()
12022 {
12023 #if !WIN32
12024     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12025     FILE *commandOutput;
12026     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12027     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12028     int nBuffers;
12029     int i;
12030     int archived;
12031     char *arcDir;
12032
12033     if (! cmailMsgLoaded) {
12034         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12035         return;
12036     }
12037
12038     if (nCmailGames == nCmailResults) {
12039         DisplayError(_("No unfinished games"), 0);
12040         return;
12041     }
12042
12043 #if CMAIL_PROHIBIT_REMAIL
12044     if (cmailMailedMove) {
12045       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);
12046         DisplayError(msg, 0);
12047         return;
12048     }
12049 #endif
12050
12051     if (! (cmailMailedMove || RegisterMove())) return;
12052
12053     if (   cmailMailedMove
12054         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12055       snprintf(string, MSG_SIZ, partCommandString,
12056                appData.debugMode ? " -v" : "", appData.cmailGameName);
12057         commandOutput = popen(string, "r");
12058
12059         if (commandOutput == NULL) {
12060             DisplayError(_("Failed to invoke cmail"), 0);
12061         } else {
12062             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12063                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12064             }
12065             if (nBuffers > 1) {
12066                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12067                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12068                 nBytes = MSG_SIZ - 1;
12069             } else {
12070                 (void) memcpy(msg, buffer, nBytes);
12071             }
12072             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12073
12074             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12075                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12076
12077                 archived = TRUE;
12078                 for (i = 0; i < nCmailGames; i ++) {
12079                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12080                         archived = FALSE;
12081                     }
12082                 }
12083                 if (   archived
12084                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12085                         != NULL)) {
12086                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12087                            arcDir,
12088                            appData.cmailGameName,
12089                            gameInfo.date);
12090                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12091                     cmailMsgLoaded = FALSE;
12092                 }
12093             }
12094
12095             DisplayInformation(msg);
12096             pclose(commandOutput);
12097         }
12098     } else {
12099         if ((*cmailMsg) != '\0') {
12100             DisplayInformation(cmailMsg);
12101         }
12102     }
12103
12104     return;
12105 #endif /* !WIN32 */
12106 }
12107
12108 char *
12109 CmailMsg()
12110 {
12111 #if WIN32
12112     return NULL;
12113 #else
12114     int  prependComma = 0;
12115     char number[5];
12116     char string[MSG_SIZ];       /* Space for game-list */
12117     int  i;
12118
12119     if (!cmailMsgLoaded) return "";
12120
12121     if (cmailMailedMove) {
12122       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12123     } else {
12124         /* Create a list of games left */
12125       snprintf(string, MSG_SIZ, "[");
12126         for (i = 0; i < nCmailGames; i ++) {
12127             if (! (   cmailMoveRegistered[i]
12128                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12129                 if (prependComma) {
12130                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12131                 } else {
12132                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12133                     prependComma = 1;
12134                 }
12135
12136                 strcat(string, number);
12137             }
12138         }
12139         strcat(string, "]");
12140
12141         if (nCmailMovesRegistered + nCmailResults == 0) {
12142             switch (nCmailGames) {
12143               case 1:
12144                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12145                 break;
12146
12147               case 2:
12148                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12149                 break;
12150
12151               default:
12152                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12153                          nCmailGames);
12154                 break;
12155             }
12156         } else {
12157             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12158               case 1:
12159                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12160                          string);
12161                 break;
12162
12163               case 0:
12164                 if (nCmailResults == nCmailGames) {
12165                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12166                 } else {
12167                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12168                 }
12169                 break;
12170
12171               default:
12172                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12173                          string);
12174             }
12175         }
12176     }
12177     return cmailMsg;
12178 #endif /* WIN32 */
12179 }
12180
12181 void
12182 ResetGameEvent()
12183 {
12184     if (gameMode == Training)
12185       SetTrainingModeOff();
12186
12187     Reset(TRUE, TRUE);
12188     cmailMsgLoaded = FALSE;
12189     if (appData.icsActive) {
12190       SendToICS(ics_prefix);
12191       SendToICS("refresh\n");
12192     }
12193 }
12194
12195 void
12196 ExitEvent(status)
12197      int status;
12198 {
12199     exiting++;
12200     if (exiting > 2) {
12201       /* Give up on clean exit */
12202       exit(status);
12203     }
12204     if (exiting > 1) {
12205       /* Keep trying for clean exit */
12206       return;
12207     }
12208
12209     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12210
12211     if (telnetISR != NULL) {
12212       RemoveInputSource(telnetISR);
12213     }
12214     if (icsPR != NoProc) {
12215       DestroyChildProcess(icsPR, TRUE);
12216     }
12217
12218     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12219     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12220
12221     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12222     /* make sure this other one finishes before killing it!                  */
12223     if(endingGame) { int count = 0;
12224         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12225         while(endingGame && count++ < 10) DoSleep(1);
12226         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12227     }
12228
12229     /* Kill off chess programs */
12230     if (first.pr != NoProc) {
12231         ExitAnalyzeMode();
12232
12233         DoSleep( appData.delayBeforeQuit );
12234         SendToProgram("quit\n", &first);
12235         DoSleep( appData.delayAfterQuit );
12236         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12237     }
12238     if (second.pr != NoProc) {
12239         DoSleep( appData.delayBeforeQuit );
12240         SendToProgram("quit\n", &second);
12241         DoSleep( appData.delayAfterQuit );
12242         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12243     }
12244     if (first.isr != NULL) {
12245         RemoveInputSource(first.isr);
12246     }
12247     if (second.isr != NULL) {
12248         RemoveInputSource(second.isr);
12249     }
12250
12251     ShutDownFrontEnd();
12252     exit(status);
12253 }
12254
12255 void
12256 PauseEvent()
12257 {
12258     if (appData.debugMode)
12259         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12260     if (pausing) {
12261         pausing = FALSE;
12262         ModeHighlight();
12263         if (gameMode == MachinePlaysWhite ||
12264             gameMode == MachinePlaysBlack) {
12265             StartClocks();
12266         } else {
12267             DisplayBothClocks();
12268         }
12269         if (gameMode == PlayFromGameFile) {
12270             if (appData.timeDelay >= 0)
12271                 AutoPlayGameLoop();
12272         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12273             Reset(FALSE, TRUE);
12274             SendToICS(ics_prefix);
12275             SendToICS("refresh\n");
12276         } else if (currentMove < forwardMostMove) {
12277             ForwardInner(forwardMostMove);
12278         }
12279         pauseExamInvalid = FALSE;
12280     } else {
12281         switch (gameMode) {
12282           default:
12283             return;
12284           case IcsExamining:
12285             pauseExamForwardMostMove = forwardMostMove;
12286             pauseExamInvalid = FALSE;
12287             /* fall through */
12288           case IcsObserving:
12289           case IcsPlayingWhite:
12290           case IcsPlayingBlack:
12291             pausing = TRUE;
12292             ModeHighlight();
12293             return;
12294           case PlayFromGameFile:
12295             (void) StopLoadGameTimer();
12296             pausing = TRUE;
12297             ModeHighlight();
12298             break;
12299           case BeginningOfGame:
12300             if (appData.icsActive) return;
12301             /* else fall through */
12302           case MachinePlaysWhite:
12303           case MachinePlaysBlack:
12304           case TwoMachinesPlay:
12305             if (forwardMostMove == 0)
12306               return;           /* don't pause if no one has moved */
12307             if ((gameMode == MachinePlaysWhite &&
12308                  !WhiteOnMove(forwardMostMove)) ||
12309                 (gameMode == MachinePlaysBlack &&
12310                  WhiteOnMove(forwardMostMove))) {
12311                 StopClocks();
12312             }
12313             pausing = TRUE;
12314             ModeHighlight();
12315             break;
12316         }
12317     }
12318 }
12319
12320 void
12321 EditCommentEvent()
12322 {
12323     char title[MSG_SIZ];
12324
12325     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12326       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12327     } else {
12328       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12329                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12330                parseList[currentMove - 1]);
12331     }
12332
12333     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12334 }
12335
12336
12337 void
12338 EditTagsEvent()
12339 {
12340     char *tags = PGNTags(&gameInfo);
12341     bookUp = FALSE;
12342     EditTagsPopUp(tags, NULL);
12343     free(tags);
12344 }
12345
12346 void
12347 AnalyzeModeEvent()
12348 {
12349     if (appData.noChessProgram || gameMode == AnalyzeMode)
12350       return;
12351
12352     if (gameMode != AnalyzeFile) {
12353         if (!appData.icsEngineAnalyze) {
12354                EditGameEvent();
12355                if (gameMode != EditGame) return;
12356         }
12357         ResurrectChessProgram();
12358         SendToProgram("analyze\n", &first);
12359         first.analyzing = TRUE;
12360         /*first.maybeThinking = TRUE;*/
12361         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12362         EngineOutputPopUp();
12363     }
12364     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12365     pausing = FALSE;
12366     ModeHighlight();
12367     SetGameInfo();
12368
12369     StartAnalysisClock();
12370     GetTimeMark(&lastNodeCountTime);
12371     lastNodeCount = 0;
12372 }
12373
12374 void
12375 AnalyzeFileEvent()
12376 {
12377     if (appData.noChessProgram || gameMode == AnalyzeFile)
12378       return;
12379
12380     if (gameMode != AnalyzeMode) {
12381         EditGameEvent();
12382         if (gameMode != EditGame) return;
12383         ResurrectChessProgram();
12384         SendToProgram("analyze\n", &first);
12385         first.analyzing = TRUE;
12386         /*first.maybeThinking = TRUE;*/
12387         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12388         EngineOutputPopUp();
12389     }
12390     gameMode = AnalyzeFile;
12391     pausing = FALSE;
12392     ModeHighlight();
12393     SetGameInfo();
12394
12395     StartAnalysisClock();
12396     GetTimeMark(&lastNodeCountTime);
12397     lastNodeCount = 0;
12398 }
12399
12400 void
12401 MachineWhiteEvent()
12402 {
12403     char buf[MSG_SIZ];
12404     char *bookHit = NULL;
12405
12406     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12407       return;
12408
12409
12410     if (gameMode == PlayFromGameFile ||
12411         gameMode == TwoMachinesPlay  ||
12412         gameMode == Training         ||
12413         gameMode == AnalyzeMode      ||
12414         gameMode == EndOfGame)
12415         EditGameEvent();
12416
12417     if (gameMode == EditPosition)
12418         EditPositionDone(TRUE);
12419
12420     if (!WhiteOnMove(currentMove)) {
12421         DisplayError(_("It is not White's turn"), 0);
12422         return;
12423     }
12424
12425     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12426       ExitAnalyzeMode();
12427
12428     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12429         gameMode == AnalyzeFile)
12430         TruncateGame();
12431
12432     ResurrectChessProgram();    /* in case it isn't running */
12433     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12434         gameMode = MachinePlaysWhite;
12435         ResetClocks();
12436     } else
12437     gameMode = MachinePlaysWhite;
12438     pausing = FALSE;
12439     ModeHighlight();
12440     SetGameInfo();
12441     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12442     DisplayTitle(buf);
12443     if (first.sendName) {
12444       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12445       SendToProgram(buf, &first);
12446     }
12447     if (first.sendTime) {
12448       if (first.useColors) {
12449         SendToProgram("black\n", &first); /*gnu kludge*/
12450       }
12451       SendTimeRemaining(&first, TRUE);
12452     }
12453     if (first.useColors) {
12454       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12455     }
12456     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12457     SetMachineThinkingEnables();
12458     first.maybeThinking = TRUE;
12459     StartClocks();
12460     firstMove = FALSE;
12461
12462     if (appData.autoFlipView && !flipView) {
12463       flipView = !flipView;
12464       DrawPosition(FALSE, NULL);
12465       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12466     }
12467
12468     if(bookHit) { // [HGM] book: simulate book reply
12469         static char bookMove[MSG_SIZ]; // a bit generous?
12470
12471         programStats.nodes = programStats.depth = programStats.time =
12472         programStats.score = programStats.got_only_move = 0;
12473         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12474
12475         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12476         strcat(bookMove, bookHit);
12477         HandleMachineMove(bookMove, &first);
12478     }
12479 }
12480
12481 void
12482 MachineBlackEvent()
12483 {
12484   char buf[MSG_SIZ];
12485   char *bookHit = NULL;
12486
12487     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12488         return;
12489
12490
12491     if (gameMode == PlayFromGameFile ||
12492         gameMode == TwoMachinesPlay  ||
12493         gameMode == Training         ||
12494         gameMode == AnalyzeMode      ||
12495         gameMode == EndOfGame)
12496         EditGameEvent();
12497
12498     if (gameMode == EditPosition)
12499         EditPositionDone(TRUE);
12500
12501     if (WhiteOnMove(currentMove)) {
12502         DisplayError(_("It is not Black's turn"), 0);
12503         return;
12504     }
12505
12506     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12507       ExitAnalyzeMode();
12508
12509     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12510         gameMode == AnalyzeFile)
12511         TruncateGame();
12512
12513     ResurrectChessProgram();    /* in case it isn't running */
12514     gameMode = MachinePlaysBlack;
12515     pausing = FALSE;
12516     ModeHighlight();
12517     SetGameInfo();
12518     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12519     DisplayTitle(buf);
12520     if (first.sendName) {
12521       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12522       SendToProgram(buf, &first);
12523     }
12524     if (first.sendTime) {
12525       if (first.useColors) {
12526         SendToProgram("white\n", &first); /*gnu kludge*/
12527       }
12528       SendTimeRemaining(&first, FALSE);
12529     }
12530     if (first.useColors) {
12531       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12532     }
12533     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12534     SetMachineThinkingEnables();
12535     first.maybeThinking = TRUE;
12536     StartClocks();
12537
12538     if (appData.autoFlipView && flipView) {
12539       flipView = !flipView;
12540       DrawPosition(FALSE, NULL);
12541       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12542     }
12543     if(bookHit) { // [HGM] book: simulate book reply
12544         static char bookMove[MSG_SIZ]; // a bit generous?
12545
12546         programStats.nodes = programStats.depth = programStats.time =
12547         programStats.score = programStats.got_only_move = 0;
12548         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12549
12550         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12551         strcat(bookMove, bookHit);
12552         HandleMachineMove(bookMove, &first);
12553     }
12554 }
12555
12556
12557 void
12558 DisplayTwoMachinesTitle()
12559 {
12560     char buf[MSG_SIZ];
12561     if (appData.matchGames > 0) {
12562         if (first.twoMachinesColor[0] == 'w') {
12563           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12564                    gameInfo.white, gameInfo.black,
12565                    first.matchWins, second.matchWins,
12566                    matchGame - 1 - (first.matchWins + second.matchWins));
12567         } else {
12568           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12569                    gameInfo.white, gameInfo.black,
12570                    second.matchWins, first.matchWins,
12571                    matchGame - 1 - (first.matchWins + second.matchWins));
12572         }
12573     } else {
12574       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12575     }
12576     DisplayTitle(buf);
12577 }
12578
12579 void
12580 SettingsMenuIfReady()
12581 {
12582   if (second.lastPing != second.lastPong) {
12583     DisplayMessage("", _("Waiting for second chess program"));
12584     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12585     return;
12586   }
12587   ThawUI();
12588   DisplayMessage("", "");
12589   SettingsPopUp(&second);
12590 }
12591
12592 int
12593 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12594 {
12595     char buf[MSG_SIZ];
12596     if (cps->pr == NULL) {
12597         StartChessProgram(cps);
12598         if (cps->protocolVersion == 1) {
12599           retry();
12600         } else {
12601           /* kludge: allow timeout for initial "feature" command */
12602           FreezeUI();
12603           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12604           DisplayMessage("", buf);
12605           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12606         }
12607         return 1;
12608     }
12609     return 0;
12610 }
12611
12612 void
12613 TwoMachinesEvent P((void))
12614 {
12615     int i;
12616     char buf[MSG_SIZ];
12617     ChessProgramState *onmove;
12618     char *bookHit = NULL;
12619     static int stalling = 0;
12620     TimeMark now;
12621     long wait;
12622
12623     if (appData.noChessProgram) return;
12624
12625     switch (gameMode) {
12626       case TwoMachinesPlay:
12627         return;
12628       case MachinePlaysWhite:
12629       case MachinePlaysBlack:
12630         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12631             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12632             return;
12633         }
12634         /* fall through */
12635       case BeginningOfGame:
12636       case PlayFromGameFile:
12637       case EndOfGame:
12638         EditGameEvent();
12639         if (gameMode != EditGame) return;
12640         break;
12641       case EditPosition:
12642         EditPositionDone(TRUE);
12643         break;
12644       case AnalyzeMode:
12645       case AnalyzeFile:
12646         ExitAnalyzeMode();
12647         break;
12648       case EditGame:
12649       default:
12650         break;
12651     }
12652
12653 //    forwardMostMove = currentMove;
12654     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12655
12656     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12657
12658     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12659     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12660       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12661       return;
12662     }
12663     if(!stalling) {
12664       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12665       SendToProgram("force\n", &second);
12666       stalling = 1;
12667       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12668       return;
12669     }
12670     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12671     if(appData.matchPause>10000 || appData.matchPause<10)
12672                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12673     wait = SubtractTimeMarks(&now, &pauseStart);
12674     if(wait < appData.matchPause) {
12675         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12676         return;
12677     }
12678     stalling = 0;
12679     DisplayMessage("", "");
12680     if (startedFromSetupPosition) {
12681         SendBoard(&second, backwardMostMove);
12682     if (appData.debugMode) {
12683         fprintf(debugFP, "Two Machines\n");
12684     }
12685     }
12686     for (i = backwardMostMove; i < forwardMostMove; i++) {
12687         SendMoveToProgram(i, &second);
12688     }
12689
12690     gameMode = TwoMachinesPlay;
12691     pausing = FALSE;
12692     ModeHighlight();
12693     SetGameInfo();
12694     DisplayTwoMachinesTitle();
12695     firstMove = TRUE;
12696     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12697         onmove = &first;
12698     } else {
12699         onmove = &second;
12700     }
12701     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12702     SendToProgram(first.computerString, &first);
12703     if (first.sendName) {
12704       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12705       SendToProgram(buf, &first);
12706     }
12707     SendToProgram(second.computerString, &second);
12708     if (second.sendName) {
12709       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12710       SendToProgram(buf, &second);
12711     }
12712
12713     ResetClocks();
12714     if (!first.sendTime || !second.sendTime) {
12715         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12716         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12717     }
12718     if (onmove->sendTime) {
12719       if (onmove->useColors) {
12720         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12721       }
12722       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12723     }
12724     if (onmove->useColors) {
12725       SendToProgram(onmove->twoMachinesColor, onmove);
12726     }
12727     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12728 //    SendToProgram("go\n", onmove);
12729     onmove->maybeThinking = TRUE;
12730     SetMachineThinkingEnables();
12731
12732     StartClocks();
12733
12734     if(bookHit) { // [HGM] book: simulate book reply
12735         static char bookMove[MSG_SIZ]; // a bit generous?
12736
12737         programStats.nodes = programStats.depth = programStats.time =
12738         programStats.score = programStats.got_only_move = 0;
12739         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12740
12741         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12742         strcat(bookMove, bookHit);
12743         savedMessage = bookMove; // args for deferred call
12744         savedState = onmove;
12745         ScheduleDelayedEvent(DeferredBookMove, 1);
12746     }
12747 }
12748
12749 void
12750 TrainingEvent()
12751 {
12752     if (gameMode == Training) {
12753       SetTrainingModeOff();
12754       gameMode = PlayFromGameFile;
12755       DisplayMessage("", _("Training mode off"));
12756     } else {
12757       gameMode = Training;
12758       animateTraining = appData.animate;
12759
12760       /* make sure we are not already at the end of the game */
12761       if (currentMove < forwardMostMove) {
12762         SetTrainingModeOn();
12763         DisplayMessage("", _("Training mode on"));
12764       } else {
12765         gameMode = PlayFromGameFile;
12766         DisplayError(_("Already at end of game"), 0);
12767       }
12768     }
12769     ModeHighlight();
12770 }
12771
12772 void
12773 IcsClientEvent()
12774 {
12775     if (!appData.icsActive) return;
12776     switch (gameMode) {
12777       case IcsPlayingWhite:
12778       case IcsPlayingBlack:
12779       case IcsObserving:
12780       case IcsIdle:
12781       case BeginningOfGame:
12782       case IcsExamining:
12783         return;
12784
12785       case EditGame:
12786         break;
12787
12788       case EditPosition:
12789         EditPositionDone(TRUE);
12790         break;
12791
12792       case AnalyzeMode:
12793       case AnalyzeFile:
12794         ExitAnalyzeMode();
12795         break;
12796
12797       default:
12798         EditGameEvent();
12799         break;
12800     }
12801
12802     gameMode = IcsIdle;
12803     ModeHighlight();
12804     return;
12805 }
12806
12807
12808 void
12809 EditGameEvent()
12810 {
12811     int i;
12812
12813     switch (gameMode) {
12814       case Training:
12815         SetTrainingModeOff();
12816         break;
12817       case MachinePlaysWhite:
12818       case MachinePlaysBlack:
12819       case BeginningOfGame:
12820         SendToProgram("force\n", &first);
12821         SetUserThinkingEnables();
12822         break;
12823       case PlayFromGameFile:
12824         (void) StopLoadGameTimer();
12825         if (gameFileFP != NULL) {
12826             gameFileFP = NULL;
12827         }
12828         break;
12829       case EditPosition:
12830         EditPositionDone(TRUE);
12831         break;
12832       case AnalyzeMode:
12833       case AnalyzeFile:
12834         ExitAnalyzeMode();
12835         SendToProgram("force\n", &first);
12836         break;
12837       case TwoMachinesPlay:
12838         GameEnds(EndOfFile, NULL, GE_PLAYER);
12839         ResurrectChessProgram();
12840         SetUserThinkingEnables();
12841         break;
12842       case EndOfGame:
12843         ResurrectChessProgram();
12844         break;
12845       case IcsPlayingBlack:
12846       case IcsPlayingWhite:
12847         DisplayError(_("Warning: You are still playing a game"), 0);
12848         break;
12849       case IcsObserving:
12850         DisplayError(_("Warning: You are still observing a game"), 0);
12851         break;
12852       case IcsExamining:
12853         DisplayError(_("Warning: You are still examining a game"), 0);
12854         break;
12855       case IcsIdle:
12856         break;
12857       case EditGame:
12858       default:
12859         return;
12860     }
12861
12862     pausing = FALSE;
12863     StopClocks();
12864     first.offeredDraw = second.offeredDraw = 0;
12865
12866     if (gameMode == PlayFromGameFile) {
12867         whiteTimeRemaining = timeRemaining[0][currentMove];
12868         blackTimeRemaining = timeRemaining[1][currentMove];
12869         DisplayTitle("");
12870     }
12871
12872     if (gameMode == MachinePlaysWhite ||
12873         gameMode == MachinePlaysBlack ||
12874         gameMode == TwoMachinesPlay ||
12875         gameMode == EndOfGame) {
12876         i = forwardMostMove;
12877         while (i > currentMove) {
12878             SendToProgram("undo\n", &first);
12879             i--;
12880         }
12881         whiteTimeRemaining = timeRemaining[0][currentMove];
12882         blackTimeRemaining = timeRemaining[1][currentMove];
12883         DisplayBothClocks();
12884         if (whiteFlag || blackFlag) {
12885             whiteFlag = blackFlag = 0;
12886         }
12887         DisplayTitle("");
12888     }
12889
12890     gameMode = EditGame;
12891     ModeHighlight();
12892     SetGameInfo();
12893 }
12894
12895
12896 void
12897 EditPositionEvent()
12898 {
12899     if (gameMode == EditPosition) {
12900         EditGameEvent();
12901         return;
12902     }
12903
12904     EditGameEvent();
12905     if (gameMode != EditGame) return;
12906
12907     gameMode = EditPosition;
12908     ModeHighlight();
12909     SetGameInfo();
12910     if (currentMove > 0)
12911       CopyBoard(boards[0], boards[currentMove]);
12912
12913     blackPlaysFirst = !WhiteOnMove(currentMove);
12914     ResetClocks();
12915     currentMove = forwardMostMove = backwardMostMove = 0;
12916     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12917     DisplayMove(-1);
12918 }
12919
12920 void
12921 ExitAnalyzeMode()
12922 {
12923     /* [DM] icsEngineAnalyze - possible call from other functions */
12924     if (appData.icsEngineAnalyze) {
12925         appData.icsEngineAnalyze = FALSE;
12926
12927         DisplayMessage("",_("Close ICS engine analyze..."));
12928     }
12929     if (first.analysisSupport && first.analyzing) {
12930       SendToProgram("exit\n", &first);
12931       first.analyzing = FALSE;
12932     }
12933     thinkOutput[0] = NULLCHAR;
12934 }
12935
12936 void
12937 EditPositionDone(Boolean fakeRights)
12938 {
12939     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12940
12941     startedFromSetupPosition = TRUE;
12942     InitChessProgram(&first, FALSE);
12943     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12944       boards[0][EP_STATUS] = EP_NONE;
12945       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12946     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12947         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12948         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12949       } else boards[0][CASTLING][2] = NoRights;
12950     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12951         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12952         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12953       } else boards[0][CASTLING][5] = NoRights;
12954     }
12955     SendToProgram("force\n", &first);
12956     if (blackPlaysFirst) {
12957         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12958         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12959         currentMove = forwardMostMove = backwardMostMove = 1;
12960         CopyBoard(boards[1], boards[0]);
12961     } else {
12962         currentMove = forwardMostMove = backwardMostMove = 0;
12963     }
12964     SendBoard(&first, forwardMostMove);
12965     if (appData.debugMode) {
12966         fprintf(debugFP, "EditPosDone\n");
12967     }
12968     DisplayTitle("");
12969     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12970     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12971     gameMode = EditGame;
12972     ModeHighlight();
12973     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12974     ClearHighlights(); /* [AS] */
12975 }
12976
12977 /* Pause for `ms' milliseconds */
12978 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12979 void
12980 TimeDelay(ms)
12981      long ms;
12982 {
12983     TimeMark m1, m2;
12984
12985     GetTimeMark(&m1);
12986     do {
12987         GetTimeMark(&m2);
12988     } while (SubtractTimeMarks(&m2, &m1) < ms);
12989 }
12990
12991 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12992 void
12993 SendMultiLineToICS(buf)
12994      char *buf;
12995 {
12996     char temp[MSG_SIZ+1], *p;
12997     int len;
12998
12999     len = strlen(buf);
13000     if (len > MSG_SIZ)
13001       len = MSG_SIZ;
13002
13003     strncpy(temp, buf, len);
13004     temp[len] = 0;
13005
13006     p = temp;
13007     while (*p) {
13008         if (*p == '\n' || *p == '\r')
13009           *p = ' ';
13010         ++p;
13011     }
13012
13013     strcat(temp, "\n");
13014     SendToICS(temp);
13015     SendToPlayer(temp, strlen(temp));
13016 }
13017
13018 void
13019 SetWhiteToPlayEvent()
13020 {
13021     if (gameMode == EditPosition) {
13022         blackPlaysFirst = FALSE;
13023         DisplayBothClocks();    /* works because currentMove is 0 */
13024     } else if (gameMode == IcsExamining) {
13025         SendToICS(ics_prefix);
13026         SendToICS("tomove white\n");
13027     }
13028 }
13029
13030 void
13031 SetBlackToPlayEvent()
13032 {
13033     if (gameMode == EditPosition) {
13034         blackPlaysFirst = TRUE;
13035         currentMove = 1;        /* kludge */
13036         DisplayBothClocks();
13037         currentMove = 0;
13038     } else if (gameMode == IcsExamining) {
13039         SendToICS(ics_prefix);
13040         SendToICS("tomove black\n");
13041     }
13042 }
13043
13044 void
13045 EditPositionMenuEvent(selection, x, y)
13046      ChessSquare selection;
13047      int x, y;
13048 {
13049     char buf[MSG_SIZ];
13050     ChessSquare piece = boards[0][y][x];
13051
13052     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13053
13054     switch (selection) {
13055       case ClearBoard:
13056         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13057             SendToICS(ics_prefix);
13058             SendToICS("bsetup clear\n");
13059         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13060             SendToICS(ics_prefix);
13061             SendToICS("clearboard\n");
13062         } else {
13063             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13064                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13065                 for (y = 0; y < BOARD_HEIGHT; y++) {
13066                     if (gameMode == IcsExamining) {
13067                         if (boards[currentMove][y][x] != EmptySquare) {
13068                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13069                                     AAA + x, ONE + y);
13070                             SendToICS(buf);
13071                         }
13072                     } else {
13073                         boards[0][y][x] = p;
13074                     }
13075                 }
13076             }
13077         }
13078         if (gameMode == EditPosition) {
13079             DrawPosition(FALSE, boards[0]);
13080         }
13081         break;
13082
13083       case WhitePlay:
13084         SetWhiteToPlayEvent();
13085         break;
13086
13087       case BlackPlay:
13088         SetBlackToPlayEvent();
13089         break;
13090
13091       case EmptySquare:
13092         if (gameMode == IcsExamining) {
13093             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13094             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13095             SendToICS(buf);
13096         } else {
13097             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13098                 if(x == BOARD_LEFT-2) {
13099                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13100                     boards[0][y][1] = 0;
13101                 } else
13102                 if(x == BOARD_RGHT+1) {
13103                     if(y >= gameInfo.holdingsSize) break;
13104                     boards[0][y][BOARD_WIDTH-2] = 0;
13105                 } else break;
13106             }
13107             boards[0][y][x] = EmptySquare;
13108             DrawPosition(FALSE, boards[0]);
13109         }
13110         break;
13111
13112       case PromotePiece:
13113         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13114            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13115             selection = (ChessSquare) (PROMOTED piece);
13116         } else if(piece == EmptySquare) selection = WhiteSilver;
13117         else selection = (ChessSquare)((int)piece - 1);
13118         goto defaultlabel;
13119
13120       case DemotePiece:
13121         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13122            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13123             selection = (ChessSquare) (DEMOTED piece);
13124         } else if(piece == EmptySquare) selection = BlackSilver;
13125         else selection = (ChessSquare)((int)piece + 1);
13126         goto defaultlabel;
13127
13128       case WhiteQueen:
13129       case BlackQueen:
13130         if(gameInfo.variant == VariantShatranj ||
13131            gameInfo.variant == VariantXiangqi  ||
13132            gameInfo.variant == VariantCourier  ||
13133            gameInfo.variant == VariantMakruk     )
13134             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13135         goto defaultlabel;
13136
13137       case WhiteKing:
13138       case BlackKing:
13139         if(gameInfo.variant == VariantXiangqi)
13140             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13141         if(gameInfo.variant == VariantKnightmate)
13142             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13143       default:
13144         defaultlabel:
13145         if (gameMode == IcsExamining) {
13146             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13147             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13148                      PieceToChar(selection), AAA + x, ONE + y);
13149             SendToICS(buf);
13150         } else {
13151             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13152                 int n;
13153                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13154                     n = PieceToNumber(selection - BlackPawn);
13155                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13156                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13157                     boards[0][BOARD_HEIGHT-1-n][1]++;
13158                 } else
13159                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13160                     n = PieceToNumber(selection);
13161                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13162                     boards[0][n][BOARD_WIDTH-1] = selection;
13163                     boards[0][n][BOARD_WIDTH-2]++;
13164                 }
13165             } else
13166             boards[0][y][x] = selection;
13167             DrawPosition(TRUE, boards[0]);
13168         }
13169         break;
13170     }
13171 }
13172
13173
13174 void
13175 DropMenuEvent(selection, x, y)
13176      ChessSquare selection;
13177      int x, y;
13178 {
13179     ChessMove moveType;
13180
13181     switch (gameMode) {
13182       case IcsPlayingWhite:
13183       case MachinePlaysBlack:
13184         if (!WhiteOnMove(currentMove)) {
13185             DisplayMoveError(_("It is Black's turn"));
13186             return;
13187         }
13188         moveType = WhiteDrop;
13189         break;
13190       case IcsPlayingBlack:
13191       case MachinePlaysWhite:
13192         if (WhiteOnMove(currentMove)) {
13193             DisplayMoveError(_("It is White's turn"));
13194             return;
13195         }
13196         moveType = BlackDrop;
13197         break;
13198       case EditGame:
13199         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13200         break;
13201       default:
13202         return;
13203     }
13204
13205     if (moveType == BlackDrop && selection < BlackPawn) {
13206       selection = (ChessSquare) ((int) selection
13207                                  + (int) BlackPawn - (int) WhitePawn);
13208     }
13209     if (boards[currentMove][y][x] != EmptySquare) {
13210         DisplayMoveError(_("That square is occupied"));
13211         return;
13212     }
13213
13214     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13215 }
13216
13217 void
13218 AcceptEvent()
13219 {
13220     /* Accept a pending offer of any kind from opponent */
13221
13222     if (appData.icsActive) {
13223         SendToICS(ics_prefix);
13224         SendToICS("accept\n");
13225     } else if (cmailMsgLoaded) {
13226         if (currentMove == cmailOldMove &&
13227             commentList[cmailOldMove] != NULL &&
13228             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13229                    "Black offers a draw" : "White offers a draw")) {
13230             TruncateGame();
13231             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13232             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13233         } else {
13234             DisplayError(_("There is no pending offer on this move"), 0);
13235             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13236         }
13237     } else {
13238         /* Not used for offers from chess program */
13239     }
13240 }
13241
13242 void
13243 DeclineEvent()
13244 {
13245     /* Decline a pending offer of any kind from opponent */
13246
13247     if (appData.icsActive) {
13248         SendToICS(ics_prefix);
13249         SendToICS("decline\n");
13250     } else if (cmailMsgLoaded) {
13251         if (currentMove == cmailOldMove &&
13252             commentList[cmailOldMove] != NULL &&
13253             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13254                    "Black offers a draw" : "White offers a draw")) {
13255 #ifdef NOTDEF
13256             AppendComment(cmailOldMove, "Draw declined", TRUE);
13257             DisplayComment(cmailOldMove - 1, "Draw declined");
13258 #endif /*NOTDEF*/
13259         } else {
13260             DisplayError(_("There is no pending offer on this move"), 0);
13261         }
13262     } else {
13263         /* Not used for offers from chess program */
13264     }
13265 }
13266
13267 void
13268 RematchEvent()
13269 {
13270     /* Issue ICS rematch command */
13271     if (appData.icsActive) {
13272         SendToICS(ics_prefix);
13273         SendToICS("rematch\n");
13274     }
13275 }
13276
13277 void
13278 CallFlagEvent()
13279 {
13280     /* Call your opponent's flag (claim a win on time) */
13281     if (appData.icsActive) {
13282         SendToICS(ics_prefix);
13283         SendToICS("flag\n");
13284     } else {
13285         switch (gameMode) {
13286           default:
13287             return;
13288           case MachinePlaysWhite:
13289             if (whiteFlag) {
13290                 if (blackFlag)
13291                   GameEnds(GameIsDrawn, "Both players ran out of time",
13292                            GE_PLAYER);
13293                 else
13294                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13295             } else {
13296                 DisplayError(_("Your opponent is not out of time"), 0);
13297             }
13298             break;
13299           case MachinePlaysBlack:
13300             if (blackFlag) {
13301                 if (whiteFlag)
13302                   GameEnds(GameIsDrawn, "Both players ran out of time",
13303                            GE_PLAYER);
13304                 else
13305                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13306             } else {
13307                 DisplayError(_("Your opponent is not out of time"), 0);
13308             }
13309             break;
13310         }
13311     }
13312 }
13313
13314 void
13315 ClockClick(int which)
13316 {       // [HGM] code moved to back-end from winboard.c
13317         if(which) { // black clock
13318           if (gameMode == EditPosition || gameMode == IcsExamining) {
13319             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13320             SetBlackToPlayEvent();
13321           } else if (gameMode == EditGame || shiftKey) {
13322             AdjustClock(which, -1);
13323           } else if (gameMode == IcsPlayingWhite ||
13324                      gameMode == MachinePlaysBlack) {
13325             CallFlagEvent();
13326           }
13327         } else { // white clock
13328           if (gameMode == EditPosition || gameMode == IcsExamining) {
13329             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13330             SetWhiteToPlayEvent();
13331           } else if (gameMode == EditGame || shiftKey) {
13332             AdjustClock(which, -1);
13333           } else if (gameMode == IcsPlayingBlack ||
13334                    gameMode == MachinePlaysWhite) {
13335             CallFlagEvent();
13336           }
13337         }
13338 }
13339
13340 void
13341 DrawEvent()
13342 {
13343     /* Offer draw or accept pending draw offer from opponent */
13344
13345     if (appData.icsActive) {
13346         /* Note: tournament rules require draw offers to be
13347            made after you make your move but before you punch
13348            your clock.  Currently ICS doesn't let you do that;
13349            instead, you immediately punch your clock after making
13350            a move, but you can offer a draw at any time. */
13351
13352         SendToICS(ics_prefix);
13353         SendToICS("draw\n");
13354         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13355     } else if (cmailMsgLoaded) {
13356         if (currentMove == cmailOldMove &&
13357             commentList[cmailOldMove] != NULL &&
13358             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13359                    "Black offers a draw" : "White offers a draw")) {
13360             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13361             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13362         } else if (currentMove == cmailOldMove + 1) {
13363             char *offer = WhiteOnMove(cmailOldMove) ?
13364               "White offers a draw" : "Black offers a draw";
13365             AppendComment(currentMove, offer, TRUE);
13366             DisplayComment(currentMove - 1, offer);
13367             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13368         } else {
13369             DisplayError(_("You must make your move before offering a draw"), 0);
13370             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13371         }
13372     } else if (first.offeredDraw) {
13373         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13374     } else {
13375         if (first.sendDrawOffers) {
13376             SendToProgram("draw\n", &first);
13377             userOfferedDraw = TRUE;
13378         }
13379     }
13380 }
13381
13382 void
13383 AdjournEvent()
13384 {
13385     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13386
13387     if (appData.icsActive) {
13388         SendToICS(ics_prefix);
13389         SendToICS("adjourn\n");
13390     } else {
13391         /* Currently GNU Chess doesn't offer or accept Adjourns */
13392     }
13393 }
13394
13395
13396 void
13397 AbortEvent()
13398 {
13399     /* Offer Abort or accept pending Abort offer from opponent */
13400
13401     if (appData.icsActive) {
13402         SendToICS(ics_prefix);
13403         SendToICS("abort\n");
13404     } else {
13405         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13406     }
13407 }
13408
13409 void
13410 ResignEvent()
13411 {
13412     /* Resign.  You can do this even if it's not your turn. */
13413
13414     if (appData.icsActive) {
13415         SendToICS(ics_prefix);
13416         SendToICS("resign\n");
13417     } else {
13418         switch (gameMode) {
13419           case MachinePlaysWhite:
13420             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13421             break;
13422           case MachinePlaysBlack:
13423             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13424             break;
13425           case EditGame:
13426             if (cmailMsgLoaded) {
13427                 TruncateGame();
13428                 if (WhiteOnMove(cmailOldMove)) {
13429                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13430                 } else {
13431                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13432                 }
13433                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13434             }
13435             break;
13436           default:
13437             break;
13438         }
13439     }
13440 }
13441
13442
13443 void
13444 StopObservingEvent()
13445 {
13446     /* Stop observing current games */
13447     SendToICS(ics_prefix);
13448     SendToICS("unobserve\n");
13449 }
13450
13451 void
13452 StopExaminingEvent()
13453 {
13454     /* Stop observing current game */
13455     SendToICS(ics_prefix);
13456     SendToICS("unexamine\n");
13457 }
13458
13459 void
13460 ForwardInner(target)
13461      int target;
13462 {
13463     int limit;
13464
13465     if (appData.debugMode)
13466         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13467                 target, currentMove, forwardMostMove);
13468
13469     if (gameMode == EditPosition)
13470       return;
13471
13472     if (gameMode == PlayFromGameFile && !pausing)
13473       PauseEvent();
13474
13475     if (gameMode == IcsExamining && pausing)
13476       limit = pauseExamForwardMostMove;
13477     else
13478       limit = forwardMostMove;
13479
13480     if (target > limit) target = limit;
13481
13482     if (target > 0 && moveList[target - 1][0]) {
13483         int fromX, fromY, toX, toY;
13484         toX = moveList[target - 1][2] - AAA;
13485         toY = moveList[target - 1][3] - ONE;
13486         if (moveList[target - 1][1] == '@') {
13487             if (appData.highlightLastMove) {
13488                 SetHighlights(-1, -1, toX, toY);
13489             }
13490         } else {
13491             fromX = moveList[target - 1][0] - AAA;
13492             fromY = moveList[target - 1][1] - ONE;
13493             if (target == currentMove + 1) {
13494                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13495             }
13496             if (appData.highlightLastMove) {
13497                 SetHighlights(fromX, fromY, toX, toY);
13498             }
13499         }
13500     }
13501     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13502         gameMode == Training || gameMode == PlayFromGameFile ||
13503         gameMode == AnalyzeFile) {
13504         while (currentMove < target) {
13505             SendMoveToProgram(currentMove++, &first);
13506         }
13507     } else {
13508         currentMove = target;
13509     }
13510
13511     if (gameMode == EditGame || gameMode == EndOfGame) {
13512         whiteTimeRemaining = timeRemaining[0][currentMove];
13513         blackTimeRemaining = timeRemaining[1][currentMove];
13514     }
13515     DisplayBothClocks();
13516     DisplayMove(currentMove - 1);
13517     DrawPosition(FALSE, boards[currentMove]);
13518     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13519     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13520         DisplayComment(currentMove - 1, commentList[currentMove]);
13521     }
13522     DisplayBook(currentMove);
13523 }
13524
13525
13526 void
13527 ForwardEvent()
13528 {
13529     if (gameMode == IcsExamining && !pausing) {
13530         SendToICS(ics_prefix);
13531         SendToICS("forward\n");
13532     } else {
13533         ForwardInner(currentMove + 1);
13534     }
13535 }
13536
13537 void
13538 ToEndEvent()
13539 {
13540     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13541         /* to optimze, we temporarily turn off analysis mode while we feed
13542          * the remaining moves to the engine. Otherwise we get analysis output
13543          * after each move.
13544          */
13545         if (first.analysisSupport) {
13546           SendToProgram("exit\nforce\n", &first);
13547           first.analyzing = FALSE;
13548         }
13549     }
13550
13551     if (gameMode == IcsExamining && !pausing) {
13552         SendToICS(ics_prefix);
13553         SendToICS("forward 999999\n");
13554     } else {
13555         ForwardInner(forwardMostMove);
13556     }
13557
13558     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13559         /* we have fed all the moves, so reactivate analysis mode */
13560         SendToProgram("analyze\n", &first);
13561         first.analyzing = TRUE;
13562         /*first.maybeThinking = TRUE;*/
13563         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13564     }
13565 }
13566
13567 void
13568 BackwardInner(target)
13569      int target;
13570 {
13571     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13572
13573     if (appData.debugMode)
13574         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13575                 target, currentMove, forwardMostMove);
13576
13577     if (gameMode == EditPosition) return;
13578     if (currentMove <= backwardMostMove) {
13579         ClearHighlights();
13580         DrawPosition(full_redraw, boards[currentMove]);
13581         return;
13582     }
13583     if (gameMode == PlayFromGameFile && !pausing)
13584       PauseEvent();
13585
13586     if (moveList[target][0]) {
13587         int fromX, fromY, toX, toY;
13588         toX = moveList[target][2] - AAA;
13589         toY = moveList[target][3] - ONE;
13590         if (moveList[target][1] == '@') {
13591             if (appData.highlightLastMove) {
13592                 SetHighlights(-1, -1, toX, toY);
13593             }
13594         } else {
13595             fromX = moveList[target][0] - AAA;
13596             fromY = moveList[target][1] - ONE;
13597             if (target == currentMove - 1) {
13598                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13599             }
13600             if (appData.highlightLastMove) {
13601                 SetHighlights(fromX, fromY, toX, toY);
13602             }
13603         }
13604     }
13605     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13606         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13607         while (currentMove > target) {
13608             SendToProgram("undo\n", &first);
13609             currentMove--;
13610         }
13611     } else {
13612         currentMove = target;
13613     }
13614
13615     if (gameMode == EditGame || gameMode == EndOfGame) {
13616         whiteTimeRemaining = timeRemaining[0][currentMove];
13617         blackTimeRemaining = timeRemaining[1][currentMove];
13618     }
13619     DisplayBothClocks();
13620     DisplayMove(currentMove - 1);
13621     DrawPosition(full_redraw, boards[currentMove]);
13622     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13623     // [HGM] PV info: routine tests if comment empty
13624     DisplayComment(currentMove - 1, commentList[currentMove]);
13625     DisplayBook(currentMove);
13626 }
13627
13628 void
13629 BackwardEvent()
13630 {
13631     if (gameMode == IcsExamining && !pausing) {
13632         SendToICS(ics_prefix);
13633         SendToICS("backward\n");
13634     } else {
13635         BackwardInner(currentMove - 1);
13636     }
13637 }
13638
13639 void
13640 ToStartEvent()
13641 {
13642     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13643         /* to optimize, we temporarily turn off analysis mode while we undo
13644          * all the moves. Otherwise we get analysis output after each undo.
13645          */
13646         if (first.analysisSupport) {
13647           SendToProgram("exit\nforce\n", &first);
13648           first.analyzing = FALSE;
13649         }
13650     }
13651
13652     if (gameMode == IcsExamining && !pausing) {
13653         SendToICS(ics_prefix);
13654         SendToICS("backward 999999\n");
13655     } else {
13656         BackwardInner(backwardMostMove);
13657     }
13658
13659     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13660         /* we have fed all the moves, so reactivate analysis mode */
13661         SendToProgram("analyze\n", &first);
13662         first.analyzing = TRUE;
13663         /*first.maybeThinking = TRUE;*/
13664         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13665     }
13666 }
13667
13668 void
13669 ToNrEvent(int to)
13670 {
13671   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13672   if (to >= forwardMostMove) to = forwardMostMove;
13673   if (to <= backwardMostMove) to = backwardMostMove;
13674   if (to < currentMove) {
13675     BackwardInner(to);
13676   } else {
13677     ForwardInner(to);
13678   }
13679 }
13680
13681 void
13682 RevertEvent(Boolean annotate)
13683 {
13684     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13685         return;
13686     }
13687     if (gameMode != IcsExamining) {
13688         DisplayError(_("You are not examining a game"), 0);
13689         return;
13690     }
13691     if (pausing) {
13692         DisplayError(_("You can't revert while pausing"), 0);
13693         return;
13694     }
13695     SendToICS(ics_prefix);
13696     SendToICS("revert\n");
13697 }
13698
13699 void
13700 RetractMoveEvent()
13701 {
13702     switch (gameMode) {
13703       case MachinePlaysWhite:
13704       case MachinePlaysBlack:
13705         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13706             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13707             return;
13708         }
13709         if (forwardMostMove < 2) return;
13710         currentMove = forwardMostMove = forwardMostMove - 2;
13711         whiteTimeRemaining = timeRemaining[0][currentMove];
13712         blackTimeRemaining = timeRemaining[1][currentMove];
13713         DisplayBothClocks();
13714         DisplayMove(currentMove - 1);
13715         ClearHighlights();/*!! could figure this out*/
13716         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13717         SendToProgram("remove\n", &first);
13718         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13719         break;
13720
13721       case BeginningOfGame:
13722       default:
13723         break;
13724
13725       case IcsPlayingWhite:
13726       case IcsPlayingBlack:
13727         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13728             SendToICS(ics_prefix);
13729             SendToICS("takeback 2\n");
13730         } else {
13731             SendToICS(ics_prefix);
13732             SendToICS("takeback 1\n");
13733         }
13734         break;
13735     }
13736 }
13737
13738 void
13739 MoveNowEvent()
13740 {
13741     ChessProgramState *cps;
13742
13743     switch (gameMode) {
13744       case MachinePlaysWhite:
13745         if (!WhiteOnMove(forwardMostMove)) {
13746             DisplayError(_("It is your turn"), 0);
13747             return;
13748         }
13749         cps = &first;
13750         break;
13751       case MachinePlaysBlack:
13752         if (WhiteOnMove(forwardMostMove)) {
13753             DisplayError(_("It is your turn"), 0);
13754             return;
13755         }
13756         cps = &first;
13757         break;
13758       case TwoMachinesPlay:
13759         if (WhiteOnMove(forwardMostMove) ==
13760             (first.twoMachinesColor[0] == 'w')) {
13761             cps = &first;
13762         } else {
13763             cps = &second;
13764         }
13765         break;
13766       case BeginningOfGame:
13767       default:
13768         return;
13769     }
13770     SendToProgram("?\n", cps);
13771 }
13772
13773 void
13774 TruncateGameEvent()
13775 {
13776     EditGameEvent();
13777     if (gameMode != EditGame) return;
13778     TruncateGame();
13779 }
13780
13781 void
13782 TruncateGame()
13783 {
13784     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13785     if (forwardMostMove > currentMove) {
13786         if (gameInfo.resultDetails != NULL) {
13787             free(gameInfo.resultDetails);
13788             gameInfo.resultDetails = NULL;
13789             gameInfo.result = GameUnfinished;
13790         }
13791         forwardMostMove = currentMove;
13792         HistorySet(parseList, backwardMostMove, forwardMostMove,
13793                    currentMove-1);
13794     }
13795 }
13796
13797 void
13798 HintEvent()
13799 {
13800     if (appData.noChessProgram) return;
13801     switch (gameMode) {
13802       case MachinePlaysWhite:
13803         if (WhiteOnMove(forwardMostMove)) {
13804             DisplayError(_("Wait until your turn"), 0);
13805             return;
13806         }
13807         break;
13808       case BeginningOfGame:
13809       case MachinePlaysBlack:
13810         if (!WhiteOnMove(forwardMostMove)) {
13811             DisplayError(_("Wait until your turn"), 0);
13812             return;
13813         }
13814         break;
13815       default:
13816         DisplayError(_("No hint available"), 0);
13817         return;
13818     }
13819     SendToProgram("hint\n", &first);
13820     hintRequested = TRUE;
13821 }
13822
13823 void
13824 BookEvent()
13825 {
13826     if (appData.noChessProgram) return;
13827     switch (gameMode) {
13828       case MachinePlaysWhite:
13829         if (WhiteOnMove(forwardMostMove)) {
13830             DisplayError(_("Wait until your turn"), 0);
13831             return;
13832         }
13833         break;
13834       case BeginningOfGame:
13835       case MachinePlaysBlack:
13836         if (!WhiteOnMove(forwardMostMove)) {
13837             DisplayError(_("Wait until your turn"), 0);
13838             return;
13839         }
13840         break;
13841       case EditPosition:
13842         EditPositionDone(TRUE);
13843         break;
13844       case TwoMachinesPlay:
13845         return;
13846       default:
13847         break;
13848     }
13849     SendToProgram("bk\n", &first);
13850     bookOutput[0] = NULLCHAR;
13851     bookRequested = TRUE;
13852 }
13853
13854 void
13855 AboutGameEvent()
13856 {
13857     char *tags = PGNTags(&gameInfo);
13858     TagsPopUp(tags, CmailMsg());
13859     free(tags);
13860 }
13861
13862 /* end button procedures */
13863
13864 void
13865 PrintPosition(fp, move)
13866      FILE *fp;
13867      int move;
13868 {
13869     int i, j;
13870
13871     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13872         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13873             char c = PieceToChar(boards[move][i][j]);
13874             fputc(c == 'x' ? '.' : c, fp);
13875             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13876         }
13877     }
13878     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13879       fprintf(fp, "white to play\n");
13880     else
13881       fprintf(fp, "black to play\n");
13882 }
13883
13884 void
13885 PrintOpponents(fp)
13886      FILE *fp;
13887 {
13888     if (gameInfo.white != NULL) {
13889         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13890     } else {
13891         fprintf(fp, "\n");
13892     }
13893 }
13894
13895 /* Find last component of program's own name, using some heuristics */
13896 void
13897 TidyProgramName(prog, host, buf)
13898      char *prog, *host, buf[MSG_SIZ];
13899 {
13900     char *p, *q;
13901     int local = (strcmp(host, "localhost") == 0);
13902     while (!local && (p = strchr(prog, ';')) != NULL) {
13903         p++;
13904         while (*p == ' ') p++;
13905         prog = p;
13906     }
13907     if (*prog == '"' || *prog == '\'') {
13908         q = strchr(prog + 1, *prog);
13909     } else {
13910         q = strchr(prog, ' ');
13911     }
13912     if (q == NULL) q = prog + strlen(prog);
13913     p = q;
13914     while (p >= prog && *p != '/' && *p != '\\') p--;
13915     p++;
13916     if(p == prog && *p == '"') p++;
13917     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13918     memcpy(buf, p, q - p);
13919     buf[q - p] = NULLCHAR;
13920     if (!local) {
13921         strcat(buf, "@");
13922         strcat(buf, host);
13923     }
13924 }
13925
13926 char *
13927 TimeControlTagValue()
13928 {
13929     char buf[MSG_SIZ];
13930     if (!appData.clockMode) {
13931       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13932     } else if (movesPerSession > 0) {
13933       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13934     } else if (timeIncrement == 0) {
13935       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13936     } else {
13937       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13938     }
13939     return StrSave(buf);
13940 }
13941
13942 void
13943 SetGameInfo()
13944 {
13945     /* This routine is used only for certain modes */
13946     VariantClass v = gameInfo.variant;
13947     ChessMove r = GameUnfinished;
13948     char *p = NULL;
13949
13950     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13951         r = gameInfo.result;
13952         p = gameInfo.resultDetails;
13953         gameInfo.resultDetails = NULL;
13954     }
13955     ClearGameInfo(&gameInfo);
13956     gameInfo.variant = v;
13957
13958     switch (gameMode) {
13959       case MachinePlaysWhite:
13960         gameInfo.event = StrSave( appData.pgnEventHeader );
13961         gameInfo.site = StrSave(HostName());
13962         gameInfo.date = PGNDate();
13963         gameInfo.round = StrSave("-");
13964         gameInfo.white = StrSave(first.tidy);
13965         gameInfo.black = StrSave(UserName());
13966         gameInfo.timeControl = TimeControlTagValue();
13967         break;
13968
13969       case MachinePlaysBlack:
13970         gameInfo.event = StrSave( appData.pgnEventHeader );
13971         gameInfo.site = StrSave(HostName());
13972         gameInfo.date = PGNDate();
13973         gameInfo.round = StrSave("-");
13974         gameInfo.white = StrSave(UserName());
13975         gameInfo.black = StrSave(first.tidy);
13976         gameInfo.timeControl = TimeControlTagValue();
13977         break;
13978
13979       case TwoMachinesPlay:
13980         gameInfo.event = StrSave( appData.pgnEventHeader );
13981         gameInfo.site = StrSave(HostName());
13982         gameInfo.date = PGNDate();
13983         if (roundNr > 0) {
13984             char buf[MSG_SIZ];
13985             snprintf(buf, MSG_SIZ, "%d", roundNr);
13986             gameInfo.round = StrSave(buf);
13987         } else {
13988             gameInfo.round = StrSave("-");
13989         }
13990         if (first.twoMachinesColor[0] == 'w') {
13991             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13992             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13993         } else {
13994             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13995             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13996         }
13997         gameInfo.timeControl = TimeControlTagValue();
13998         break;
13999
14000       case EditGame:
14001         gameInfo.event = StrSave("Edited game");
14002         gameInfo.site = StrSave(HostName());
14003         gameInfo.date = PGNDate();
14004         gameInfo.round = StrSave("-");
14005         gameInfo.white = StrSave("-");
14006         gameInfo.black = StrSave("-");
14007         gameInfo.result = r;
14008         gameInfo.resultDetails = p;
14009         break;
14010
14011       case EditPosition:
14012         gameInfo.event = StrSave("Edited position");
14013         gameInfo.site = StrSave(HostName());
14014         gameInfo.date = PGNDate();
14015         gameInfo.round = StrSave("-");
14016         gameInfo.white = StrSave("-");
14017         gameInfo.black = StrSave("-");
14018         break;
14019
14020       case IcsPlayingWhite:
14021       case IcsPlayingBlack:
14022       case IcsObserving:
14023       case IcsExamining:
14024         break;
14025
14026       case PlayFromGameFile:
14027         gameInfo.event = StrSave("Game from non-PGN file");
14028         gameInfo.site = StrSave(HostName());
14029         gameInfo.date = PGNDate();
14030         gameInfo.round = StrSave("-");
14031         gameInfo.white = StrSave("?");
14032         gameInfo.black = StrSave("?");
14033         break;
14034
14035       default:
14036         break;
14037     }
14038 }
14039
14040 void
14041 ReplaceComment(index, text)
14042      int index;
14043      char *text;
14044 {
14045     int len;
14046     char *p;
14047     float score;
14048
14049     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14050        pvInfoList[index-1].depth == len &&
14051        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14052        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14053     while (*text == '\n') text++;
14054     len = strlen(text);
14055     while (len > 0 && text[len - 1] == '\n') len--;
14056
14057     if (commentList[index] != NULL)
14058       free(commentList[index]);
14059
14060     if (len == 0) {
14061         commentList[index] = NULL;
14062         return;
14063     }
14064   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14065       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14066       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14067     commentList[index] = (char *) malloc(len + 2);
14068     strncpy(commentList[index], text, len);
14069     commentList[index][len] = '\n';
14070     commentList[index][len + 1] = NULLCHAR;
14071   } else {
14072     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14073     char *p;
14074     commentList[index] = (char *) malloc(len + 7);
14075     safeStrCpy(commentList[index], "{\n", 3);
14076     safeStrCpy(commentList[index]+2, text, len+1);
14077     commentList[index][len+2] = NULLCHAR;
14078     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14079     strcat(commentList[index], "\n}\n");
14080   }
14081 }
14082
14083 void
14084 CrushCRs(text)
14085      char *text;
14086 {
14087   char *p = text;
14088   char *q = text;
14089   char ch;
14090
14091   do {
14092     ch = *p++;
14093     if (ch == '\r') continue;
14094     *q++ = ch;
14095   } while (ch != '\0');
14096 }
14097
14098 void
14099 AppendComment(index, text, addBraces)
14100      int index;
14101      char *text;
14102      Boolean addBraces; // [HGM] braces: tells if we should add {}
14103 {
14104     int oldlen, len;
14105     char *old;
14106
14107 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14108     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14109
14110     CrushCRs(text);
14111     while (*text == '\n') text++;
14112     len = strlen(text);
14113     while (len > 0 && text[len - 1] == '\n') len--;
14114
14115     if (len == 0) return;
14116
14117     if (commentList[index] != NULL) {
14118         old = commentList[index];
14119         oldlen = strlen(old);
14120         while(commentList[index][oldlen-1] ==  '\n')
14121           commentList[index][--oldlen] = NULLCHAR;
14122         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14123         safeStrCpy(commentList[index], old, oldlen + len + 6);
14124         free(old);
14125         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14126         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14127           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14128           while (*text == '\n') { text++; len--; }
14129           commentList[index][--oldlen] = NULLCHAR;
14130       }
14131         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14132         else          strcat(commentList[index], "\n");
14133         strcat(commentList[index], text);
14134         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14135         else          strcat(commentList[index], "\n");
14136     } else {
14137         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14138         if(addBraces)
14139           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14140         else commentList[index][0] = NULLCHAR;
14141         strcat(commentList[index], text);
14142         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14143         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14144     }
14145 }
14146
14147 static char * FindStr( char * text, char * sub_text )
14148 {
14149     char * result = strstr( text, sub_text );
14150
14151     if( result != NULL ) {
14152         result += strlen( sub_text );
14153     }
14154
14155     return result;
14156 }
14157
14158 /* [AS] Try to extract PV info from PGN comment */
14159 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14160 char *GetInfoFromComment( int index, char * text )
14161 {
14162     char * sep = text, *p;
14163
14164     if( text != NULL && index > 0 ) {
14165         int score = 0;
14166         int depth = 0;
14167         int time = -1, sec = 0, deci;
14168         char * s_eval = FindStr( text, "[%eval " );
14169         char * s_emt = FindStr( text, "[%emt " );
14170
14171         if( s_eval != NULL || s_emt != NULL ) {
14172             /* New style */
14173             char delim;
14174
14175             if( s_eval != NULL ) {
14176                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14177                     return text;
14178                 }
14179
14180                 if( delim != ']' ) {
14181                     return text;
14182                 }
14183             }
14184
14185             if( s_emt != NULL ) {
14186             }
14187                 return text;
14188         }
14189         else {
14190             /* We expect something like: [+|-]nnn.nn/dd */
14191             int score_lo = 0;
14192
14193             if(*text != '{') return text; // [HGM] braces: must be normal comment
14194
14195             sep = strchr( text, '/' );
14196             if( sep == NULL || sep < (text+4) ) {
14197                 return text;
14198             }
14199
14200             p = text;
14201             if(p[1] == '(') { // comment starts with PV
14202                p = strchr(p, ')'); // locate end of PV
14203                if(p == NULL || sep < p+5) return text;
14204                // at this point we have something like "{(.*) +0.23/6 ..."
14205                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14206                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14207                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14208             }
14209             time = -1; sec = -1; deci = -1;
14210             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14211                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14212                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14213                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14214                 return text;
14215             }
14216
14217             if( score_lo < 0 || score_lo >= 100 ) {
14218                 return text;
14219             }
14220
14221             if(sec >= 0) time = 600*time + 10*sec; else
14222             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14223
14224             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14225
14226             /* [HGM] PV time: now locate end of PV info */
14227             while( *++sep >= '0' && *sep <= '9'); // strip depth
14228             if(time >= 0)
14229             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14230             if(sec >= 0)
14231             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14232             if(deci >= 0)
14233             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14234             while(*sep == ' ') sep++;
14235         }
14236
14237         if( depth <= 0 ) {
14238             return text;
14239         }
14240
14241         if( time < 0 ) {
14242             time = -1;
14243         }
14244
14245         pvInfoList[index-1].depth = depth;
14246         pvInfoList[index-1].score = score;
14247         pvInfoList[index-1].time  = 10*time; // centi-sec
14248         if(*sep == '}') *sep = 0; else *--sep = '{';
14249         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14250     }
14251     return sep;
14252 }
14253
14254 void
14255 SendToProgram(message, cps)
14256      char *message;
14257      ChessProgramState *cps;
14258 {
14259     int count, outCount, error;
14260     char buf[MSG_SIZ];
14261
14262     if (cps->pr == NULL) return;
14263     Attention(cps);
14264
14265     if (appData.debugMode) {
14266         TimeMark now;
14267         GetTimeMark(&now);
14268         fprintf(debugFP, "%ld >%-6s: %s",
14269                 SubtractTimeMarks(&now, &programStartTime),
14270                 cps->which, message);
14271     }
14272
14273     count = strlen(message);
14274     outCount = OutputToProcess(cps->pr, message, count, &error);
14275     if (outCount < count && !exiting
14276                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14277       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14278       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14279         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14280             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14281                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14282                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14283                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14284             } else {
14285                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14286                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14287                 gameInfo.result = res;
14288             }
14289             gameInfo.resultDetails = StrSave(buf);
14290         }
14291         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14292         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14293     }
14294 }
14295
14296 void
14297 ReceiveFromProgram(isr, closure, message, count, error)
14298      InputSourceRef isr;
14299      VOIDSTAR closure;
14300      char *message;
14301      int count;
14302      int error;
14303 {
14304     char *end_str;
14305     char buf[MSG_SIZ];
14306     ChessProgramState *cps = (ChessProgramState *)closure;
14307
14308     if (isr != cps->isr) return; /* Killed intentionally */
14309     if (count <= 0) {
14310         if (count == 0) {
14311             RemoveInputSource(cps->isr);
14312             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14313                     _(cps->which), cps->program);
14314         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14315                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14316                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14317                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14318                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14319                 } else {
14320                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14321                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14322                     gameInfo.result = res;
14323                 }
14324                 gameInfo.resultDetails = StrSave(buf);
14325             }
14326             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14327             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14328         } else {
14329             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14330                     _(cps->which), cps->program);
14331             RemoveInputSource(cps->isr);
14332
14333             /* [AS] Program is misbehaving badly... kill it */
14334             if( count == -2 ) {
14335                 DestroyChildProcess( cps->pr, 9 );
14336                 cps->pr = NoProc;
14337             }
14338
14339             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14340         }
14341         return;
14342     }
14343
14344     if ((end_str = strchr(message, '\r')) != NULL)
14345       *end_str = NULLCHAR;
14346     if ((end_str = strchr(message, '\n')) != NULL)
14347       *end_str = NULLCHAR;
14348
14349     if (appData.debugMode) {
14350         TimeMark now; int print = 1;
14351         char *quote = ""; char c; int i;
14352
14353         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14354                 char start = message[0];
14355                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14356                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14357                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14358                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14359                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14360                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14361                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14362                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14363                    sscanf(message, "hint: %c", &c)!=1 && 
14364                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14365                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14366                     print = (appData.engineComments >= 2);
14367                 }
14368                 message[0] = start; // restore original message
14369         }
14370         if(print) {
14371                 GetTimeMark(&now);
14372                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14373                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14374                         quote,
14375                         message);
14376         }
14377     }
14378
14379     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14380     if (appData.icsEngineAnalyze) {
14381         if (strstr(message, "whisper") != NULL ||
14382              strstr(message, "kibitz") != NULL ||
14383             strstr(message, "tellics") != NULL) return;
14384     }
14385
14386     HandleMachineMove(message, cps);
14387 }
14388
14389
14390 void
14391 SendTimeControl(cps, mps, tc, inc, sd, st)
14392      ChessProgramState *cps;
14393      int mps, inc, sd, st;
14394      long tc;
14395 {
14396     char buf[MSG_SIZ];
14397     int seconds;
14398
14399     if( timeControl_2 > 0 ) {
14400         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14401             tc = timeControl_2;
14402         }
14403     }
14404     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14405     inc /= cps->timeOdds;
14406     st  /= cps->timeOdds;
14407
14408     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14409
14410     if (st > 0) {
14411       /* Set exact time per move, normally using st command */
14412       if (cps->stKludge) {
14413         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14414         seconds = st % 60;
14415         if (seconds == 0) {
14416           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14417         } else {
14418           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14419         }
14420       } else {
14421         snprintf(buf, MSG_SIZ, "st %d\n", st);
14422       }
14423     } else {
14424       /* Set conventional or incremental time control, using level command */
14425       if (seconds == 0) {
14426         /* Note old gnuchess bug -- minutes:seconds used to not work.
14427            Fixed in later versions, but still avoid :seconds
14428            when seconds is 0. */
14429         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14430       } else {
14431         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14432                  seconds, inc/1000.);
14433       }
14434     }
14435     SendToProgram(buf, cps);
14436
14437     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14438     /* Orthogonally, limit search to given depth */
14439     if (sd > 0) {
14440       if (cps->sdKludge) {
14441         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14442       } else {
14443         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14444       }
14445       SendToProgram(buf, cps);
14446     }
14447
14448     if(cps->nps >= 0) { /* [HGM] nps */
14449         if(cps->supportsNPS == FALSE)
14450           cps->nps = -1; // don't use if engine explicitly says not supported!
14451         else {
14452           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14453           SendToProgram(buf, cps);
14454         }
14455     }
14456 }
14457
14458 ChessProgramState *WhitePlayer()
14459 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14460 {
14461     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14462        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14463         return &second;
14464     return &first;
14465 }
14466
14467 void
14468 SendTimeRemaining(cps, machineWhite)
14469      ChessProgramState *cps;
14470      int /*boolean*/ machineWhite;
14471 {
14472     char message[MSG_SIZ];
14473     long time, otime;
14474
14475     /* Note: this routine must be called when the clocks are stopped
14476        or when they have *just* been set or switched; otherwise
14477        it will be off by the time since the current tick started.
14478     */
14479     if (machineWhite) {
14480         time = whiteTimeRemaining / 10;
14481         otime = blackTimeRemaining / 10;
14482     } else {
14483         time = blackTimeRemaining / 10;
14484         otime = whiteTimeRemaining / 10;
14485     }
14486     /* [HGM] translate opponent's time by time-odds factor */
14487     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14488     if (appData.debugMode) {
14489         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14490     }
14491
14492     if (time <= 0) time = 1;
14493     if (otime <= 0) otime = 1;
14494
14495     snprintf(message, MSG_SIZ, "time %ld\n", time);
14496     SendToProgram(message, cps);
14497
14498     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14499     SendToProgram(message, cps);
14500 }
14501
14502 int
14503 BoolFeature(p, name, loc, cps)
14504      char **p;
14505      char *name;
14506      int *loc;
14507      ChessProgramState *cps;
14508 {
14509   char buf[MSG_SIZ];
14510   int len = strlen(name);
14511   int val;
14512
14513   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14514     (*p) += len + 1;
14515     sscanf(*p, "%d", &val);
14516     *loc = (val != 0);
14517     while (**p && **p != ' ')
14518       (*p)++;
14519     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14520     SendToProgram(buf, cps);
14521     return TRUE;
14522   }
14523   return FALSE;
14524 }
14525
14526 int
14527 IntFeature(p, name, loc, cps)
14528      char **p;
14529      char *name;
14530      int *loc;
14531      ChessProgramState *cps;
14532 {
14533   char buf[MSG_SIZ];
14534   int len = strlen(name);
14535   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14536     (*p) += len + 1;
14537     sscanf(*p, "%d", loc);
14538     while (**p && **p != ' ') (*p)++;
14539     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14540     SendToProgram(buf, cps);
14541     return TRUE;
14542   }
14543   return FALSE;
14544 }
14545
14546 int
14547 StringFeature(p, name, loc, cps)
14548      char **p;
14549      char *name;
14550      char loc[];
14551      ChessProgramState *cps;
14552 {
14553   char buf[MSG_SIZ];
14554   int len = strlen(name);
14555   if (strncmp((*p), name, len) == 0
14556       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14557     (*p) += len + 2;
14558     sscanf(*p, "%[^\"]", loc);
14559     while (**p && **p != '\"') (*p)++;
14560     if (**p == '\"') (*p)++;
14561     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14562     SendToProgram(buf, cps);
14563     return TRUE;
14564   }
14565   return FALSE;
14566 }
14567
14568 int
14569 ParseOption(Option *opt, ChessProgramState *cps)
14570 // [HGM] options: process the string that defines an engine option, and determine
14571 // name, type, default value, and allowed value range
14572 {
14573         char *p, *q, buf[MSG_SIZ];
14574         int n, min = (-1)<<31, max = 1<<31, def;
14575
14576         if(p = strstr(opt->name, " -spin ")) {
14577             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14578             if(max < min) max = min; // enforce consistency
14579             if(def < min) def = min;
14580             if(def > max) def = max;
14581             opt->value = def;
14582             opt->min = min;
14583             opt->max = max;
14584             opt->type = Spin;
14585         } else if((p = strstr(opt->name, " -slider "))) {
14586             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14587             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14588             if(max < min) max = min; // enforce consistency
14589             if(def < min) def = min;
14590             if(def > max) def = max;
14591             opt->value = def;
14592             opt->min = min;
14593             opt->max = max;
14594             opt->type = Spin; // Slider;
14595         } else if((p = strstr(opt->name, " -string "))) {
14596             opt->textValue = p+9;
14597             opt->type = TextBox;
14598         } else if((p = strstr(opt->name, " -file "))) {
14599             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14600             opt->textValue = p+7;
14601             opt->type = FileName; // FileName;
14602         } else if((p = strstr(opt->name, " -path "))) {
14603             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14604             opt->textValue = p+7;
14605             opt->type = PathName; // PathName;
14606         } else if(p = strstr(opt->name, " -check ")) {
14607             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14608             opt->value = (def != 0);
14609             opt->type = CheckBox;
14610         } else if(p = strstr(opt->name, " -combo ")) {
14611             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14612             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14613             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14614             opt->value = n = 0;
14615             while(q = StrStr(q, " /// ")) {
14616                 n++; *q = 0;    // count choices, and null-terminate each of them
14617                 q += 5;
14618                 if(*q == '*') { // remember default, which is marked with * prefix
14619                     q++;
14620                     opt->value = n;
14621                 }
14622                 cps->comboList[cps->comboCnt++] = q;
14623             }
14624             cps->comboList[cps->comboCnt++] = NULL;
14625             opt->max = n + 1;
14626             opt->type = ComboBox;
14627         } else if(p = strstr(opt->name, " -button")) {
14628             opt->type = Button;
14629         } else if(p = strstr(opt->name, " -save")) {
14630             opt->type = SaveButton;
14631         } else return FALSE;
14632         *p = 0; // terminate option name
14633         // now look if the command-line options define a setting for this engine option.
14634         if(cps->optionSettings && cps->optionSettings[0])
14635             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14636         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14637           snprintf(buf, MSG_SIZ, "option %s", p);
14638                 if(p = strstr(buf, ",")) *p = 0;
14639                 if(q = strchr(buf, '=')) switch(opt->type) {
14640                     case ComboBox:
14641                         for(n=0; n<opt->max; n++)
14642                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14643                         break;
14644                     case TextBox:
14645                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14646                         break;
14647                     case Spin:
14648                     case CheckBox:
14649                         opt->value = atoi(q+1);
14650                     default:
14651                         break;
14652                 }
14653                 strcat(buf, "\n");
14654                 SendToProgram(buf, cps);
14655         }
14656         return TRUE;
14657 }
14658
14659 void
14660 FeatureDone(cps, val)
14661      ChessProgramState* cps;
14662      int val;
14663 {
14664   DelayedEventCallback cb = GetDelayedEvent();
14665   if ((cb == InitBackEnd3 && cps == &first) ||
14666       (cb == SettingsMenuIfReady && cps == &second) ||
14667       (cb == LoadEngine) ||
14668       (cb == TwoMachinesEventIfReady)) {
14669     CancelDelayedEvent();
14670     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14671   }
14672   cps->initDone = val;
14673 }
14674
14675 /* Parse feature command from engine */
14676 void
14677 ParseFeatures(args, cps)
14678      char* args;
14679      ChessProgramState *cps;
14680 {
14681   char *p = args;
14682   char *q;
14683   int val;
14684   char buf[MSG_SIZ];
14685
14686   for (;;) {
14687     while (*p == ' ') p++;
14688     if (*p == NULLCHAR) return;
14689
14690     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14691     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14692     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14693     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14694     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14695     if (BoolFeature(&p, "reuse", &val, cps)) {
14696       /* Engine can disable reuse, but can't enable it if user said no */
14697       if (!val) cps->reuse = FALSE;
14698       continue;
14699     }
14700     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14701     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14702       if (gameMode == TwoMachinesPlay) {
14703         DisplayTwoMachinesTitle();
14704       } else {
14705         DisplayTitle("");
14706       }
14707       continue;
14708     }
14709     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14710     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14711     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14712     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14713     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14714     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14715     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14716     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14717     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14718     if (IntFeature(&p, "done", &val, cps)) {
14719       FeatureDone(cps, val);
14720       continue;
14721     }
14722     /* Added by Tord: */
14723     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14724     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14725     /* End of additions by Tord */
14726
14727     /* [HGM] added features: */
14728     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14729     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14730     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14731     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14732     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14733     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14734     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14735         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14736           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14737             SendToProgram(buf, cps);
14738             continue;
14739         }
14740         if(cps->nrOptions >= MAX_OPTIONS) {
14741             cps->nrOptions--;
14742             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14743             DisplayError(buf, 0);
14744         }
14745         continue;
14746     }
14747     /* End of additions by HGM */
14748
14749     /* unknown feature: complain and skip */
14750     q = p;
14751     while (*q && *q != '=') q++;
14752     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14753     SendToProgram(buf, cps);
14754     p = q;
14755     if (*p == '=') {
14756       p++;
14757       if (*p == '\"') {
14758         p++;
14759         while (*p && *p != '\"') p++;
14760         if (*p == '\"') p++;
14761       } else {
14762         while (*p && *p != ' ') p++;
14763       }
14764     }
14765   }
14766
14767 }
14768
14769 void
14770 PeriodicUpdatesEvent(newState)
14771      int newState;
14772 {
14773     if (newState == appData.periodicUpdates)
14774       return;
14775
14776     appData.periodicUpdates=newState;
14777
14778     /* Display type changes, so update it now */
14779 //    DisplayAnalysis();
14780
14781     /* Get the ball rolling again... */
14782     if (newState) {
14783         AnalysisPeriodicEvent(1);
14784         StartAnalysisClock();
14785     }
14786 }
14787
14788 void
14789 PonderNextMoveEvent(newState)
14790      int newState;
14791 {
14792     if (newState == appData.ponderNextMove) return;
14793     if (gameMode == EditPosition) EditPositionDone(TRUE);
14794     if (newState) {
14795         SendToProgram("hard\n", &first);
14796         if (gameMode == TwoMachinesPlay) {
14797             SendToProgram("hard\n", &second);
14798         }
14799     } else {
14800         SendToProgram("easy\n", &first);
14801         thinkOutput[0] = NULLCHAR;
14802         if (gameMode == TwoMachinesPlay) {
14803             SendToProgram("easy\n", &second);
14804         }
14805     }
14806     appData.ponderNextMove = newState;
14807 }
14808
14809 void
14810 NewSettingEvent(option, feature, command, value)
14811      char *command;
14812      int option, value, *feature;
14813 {
14814     char buf[MSG_SIZ];
14815
14816     if (gameMode == EditPosition) EditPositionDone(TRUE);
14817     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14818     if(feature == NULL || *feature) SendToProgram(buf, &first);
14819     if (gameMode == TwoMachinesPlay) {
14820         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14821     }
14822 }
14823
14824 void
14825 ShowThinkingEvent()
14826 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14827 {
14828     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14829     int newState = appData.showThinking
14830         // [HGM] thinking: other features now need thinking output as well
14831         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14832
14833     if (oldState == newState) return;
14834     oldState = newState;
14835     if (gameMode == EditPosition) EditPositionDone(TRUE);
14836     if (oldState) {
14837         SendToProgram("post\n", &first);
14838         if (gameMode == TwoMachinesPlay) {
14839             SendToProgram("post\n", &second);
14840         }
14841     } else {
14842         SendToProgram("nopost\n", &first);
14843         thinkOutput[0] = NULLCHAR;
14844         if (gameMode == TwoMachinesPlay) {
14845             SendToProgram("nopost\n", &second);
14846         }
14847     }
14848 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14849 }
14850
14851 void
14852 AskQuestionEvent(title, question, replyPrefix, which)
14853      char *title; char *question; char *replyPrefix; char *which;
14854 {
14855   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14856   if (pr == NoProc) return;
14857   AskQuestion(title, question, replyPrefix, pr);
14858 }
14859
14860 void
14861 TypeInEvent(char firstChar)
14862 {
14863     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14864         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14865         gameMode == AnalyzeMode || gameMode == EditGame || \r
14866         gameMode == EditPosition || gameMode == IcsExamining ||\r
14867         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14868         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14869                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14870                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14871         gameMode == Training) PopUpMoveDialog(firstChar);
14872 }
14873
14874 void
14875 TypeInDoneEvent(char *move)
14876 {
14877         Board board;
14878         int n, fromX, fromY, toX, toY;
14879         char promoChar;
14880         ChessMove moveType;\r
14881
14882         // [HGM] FENedit\r
14883         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14884                 EditPositionPasteFEN(move);\r
14885                 return;\r
14886         }\r
14887         // [HGM] movenum: allow move number to be typed in any mode\r
14888         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14889           ToNrEvent(2*n-1);\r
14890           return;\r
14891         }\r
14892
14893       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14894         gameMode != Training) {\r
14895         DisplayMoveError(_("Displayed move is not current"));\r
14896       } else {\r
14897         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14898           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14899         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14900         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14901           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14902           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14903         } else {\r
14904           DisplayMoveError(_("Could not parse move"));\r
14905         }
14906       }\r
14907 }\r
14908
14909 void
14910 DisplayMove(moveNumber)
14911      int moveNumber;
14912 {
14913     char message[MSG_SIZ];
14914     char res[MSG_SIZ];
14915     char cpThinkOutput[MSG_SIZ];
14916
14917     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14918
14919     if (moveNumber == forwardMostMove - 1 ||
14920         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14921
14922         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14923
14924         if (strchr(cpThinkOutput, '\n')) {
14925             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14926         }
14927     } else {
14928         *cpThinkOutput = NULLCHAR;
14929     }
14930
14931     /* [AS] Hide thinking from human user */
14932     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14933         *cpThinkOutput = NULLCHAR;
14934         if( thinkOutput[0] != NULLCHAR ) {
14935             int i;
14936
14937             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14938                 cpThinkOutput[i] = '.';
14939             }
14940             cpThinkOutput[i] = NULLCHAR;
14941             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14942         }
14943     }
14944
14945     if (moveNumber == forwardMostMove - 1 &&
14946         gameInfo.resultDetails != NULL) {
14947         if (gameInfo.resultDetails[0] == NULLCHAR) {
14948           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14949         } else {
14950           snprintf(res, MSG_SIZ, " {%s} %s",
14951                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14952         }
14953     } else {
14954         res[0] = NULLCHAR;
14955     }
14956
14957     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14958         DisplayMessage(res, cpThinkOutput);
14959     } else {
14960       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14961                 WhiteOnMove(moveNumber) ? " " : ".. ",
14962                 parseList[moveNumber], res);
14963         DisplayMessage(message, cpThinkOutput);
14964     }
14965 }
14966
14967 void
14968 DisplayComment(moveNumber, text)
14969      int moveNumber;
14970      char *text;
14971 {
14972     char title[MSG_SIZ];
14973     char buf[8000]; // comment can be long!
14974     int score, depth;
14975
14976     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14977       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14978     } else {
14979       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14980               WhiteOnMove(moveNumber) ? " " : ".. ",
14981               parseList[moveNumber]);
14982     }
14983     // [HGM] PV info: display PV info together with (or as) comment
14984     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14985       if(text == NULL) text = "";
14986       score = pvInfoList[moveNumber].score;
14987       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14988               depth, (pvInfoList[moveNumber].time+50)/100, text);
14989       text = buf;
14990     }
14991     if (text != NULL && (appData.autoDisplayComment || commentUp))
14992         CommentPopUp(title, text);
14993 }
14994
14995 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14996  * might be busy thinking or pondering.  It can be omitted if your
14997  * gnuchess is configured to stop thinking immediately on any user
14998  * input.  However, that gnuchess feature depends on the FIONREAD
14999  * ioctl, which does not work properly on some flavors of Unix.
15000  */
15001 void
15002 Attention(cps)
15003      ChessProgramState *cps;
15004 {
15005 #if ATTENTION
15006     if (!cps->useSigint) return;
15007     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15008     switch (gameMode) {
15009       case MachinePlaysWhite:
15010       case MachinePlaysBlack:
15011       case TwoMachinesPlay:
15012       case IcsPlayingWhite:
15013       case IcsPlayingBlack:
15014       case AnalyzeMode:
15015       case AnalyzeFile:
15016         /* Skip if we know it isn't thinking */
15017         if (!cps->maybeThinking) return;
15018         if (appData.debugMode)
15019           fprintf(debugFP, "Interrupting %s\n", cps->which);
15020         InterruptChildProcess(cps->pr);
15021         cps->maybeThinking = FALSE;
15022         break;
15023       default:
15024         break;
15025     }
15026 #endif /*ATTENTION*/
15027 }
15028
15029 int
15030 CheckFlags()
15031 {
15032     if (whiteTimeRemaining <= 0) {
15033         if (!whiteFlag) {
15034             whiteFlag = TRUE;
15035             if (appData.icsActive) {
15036                 if (appData.autoCallFlag &&
15037                     gameMode == IcsPlayingBlack && !blackFlag) {
15038                   SendToICS(ics_prefix);
15039                   SendToICS("flag\n");
15040                 }
15041             } else {
15042                 if (blackFlag) {
15043                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15044                 } else {
15045                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15046                     if (appData.autoCallFlag) {
15047                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15048                         return TRUE;
15049                     }
15050                 }
15051             }
15052         }
15053     }
15054     if (blackTimeRemaining <= 0) {
15055         if (!blackFlag) {
15056             blackFlag = TRUE;
15057             if (appData.icsActive) {
15058                 if (appData.autoCallFlag &&
15059                     gameMode == IcsPlayingWhite && !whiteFlag) {
15060                   SendToICS(ics_prefix);
15061                   SendToICS("flag\n");
15062                 }
15063             } else {
15064                 if (whiteFlag) {
15065                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15066                 } else {
15067                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15068                     if (appData.autoCallFlag) {
15069                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15070                         return TRUE;
15071                     }
15072                 }
15073             }
15074         }
15075     }
15076     return FALSE;
15077 }
15078
15079 void
15080 CheckTimeControl()
15081 {
15082     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15083         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15084
15085     /*
15086      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15087      */
15088     if ( !WhiteOnMove(forwardMostMove) ) {
15089         /* White made time control */
15090         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15091         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15092         /* [HGM] time odds: correct new time quota for time odds! */
15093                                             / WhitePlayer()->timeOdds;
15094         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15095     } else {
15096         lastBlack -= blackTimeRemaining;
15097         /* Black made time control */
15098         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15099                                             / WhitePlayer()->other->timeOdds;
15100         lastWhite = whiteTimeRemaining;
15101     }
15102 }
15103
15104 void
15105 DisplayBothClocks()
15106 {
15107     int wom = gameMode == EditPosition ?
15108       !blackPlaysFirst : WhiteOnMove(currentMove);
15109     DisplayWhiteClock(whiteTimeRemaining, wom);
15110     DisplayBlackClock(blackTimeRemaining, !wom);
15111 }
15112
15113
15114 /* Timekeeping seems to be a portability nightmare.  I think everyone
15115    has ftime(), but I'm really not sure, so I'm including some ifdefs
15116    to use other calls if you don't.  Clocks will be less accurate if
15117    you have neither ftime nor gettimeofday.
15118 */
15119
15120 /* VS 2008 requires the #include outside of the function */
15121 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15122 #include <sys/timeb.h>
15123 #endif
15124
15125 /* Get the current time as a TimeMark */
15126 void
15127 GetTimeMark(tm)
15128      TimeMark *tm;
15129 {
15130 #if HAVE_GETTIMEOFDAY
15131
15132     struct timeval timeVal;
15133     struct timezone timeZone;
15134
15135     gettimeofday(&timeVal, &timeZone);
15136     tm->sec = (long) timeVal.tv_sec;
15137     tm->ms = (int) (timeVal.tv_usec / 1000L);
15138
15139 #else /*!HAVE_GETTIMEOFDAY*/
15140 #if HAVE_FTIME
15141
15142 // include <sys/timeb.h> / moved to just above start of function
15143     struct timeb timeB;
15144
15145     ftime(&timeB);
15146     tm->sec = (long) timeB.time;
15147     tm->ms = (int) timeB.millitm;
15148
15149 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15150     tm->sec = (long) time(NULL);
15151     tm->ms = 0;
15152 #endif
15153 #endif
15154 }
15155
15156 /* Return the difference in milliseconds between two
15157    time marks.  We assume the difference will fit in a long!
15158 */
15159 long
15160 SubtractTimeMarks(tm2, tm1)
15161      TimeMark *tm2, *tm1;
15162 {
15163     return 1000L*(tm2->sec - tm1->sec) +
15164            (long) (tm2->ms - tm1->ms);
15165 }
15166
15167
15168 /*
15169  * Code to manage the game clocks.
15170  *
15171  * In tournament play, black starts the clock and then white makes a move.
15172  * We give the human user a slight advantage if he is playing white---the
15173  * clocks don't run until he makes his first move, so it takes zero time.
15174  * Also, we don't account for network lag, so we could get out of sync
15175  * with GNU Chess's clock -- but then, referees are always right.
15176  */
15177
15178 static TimeMark tickStartTM;
15179 static long intendedTickLength;
15180
15181 long
15182 NextTickLength(timeRemaining)
15183      long timeRemaining;
15184 {
15185     long nominalTickLength, nextTickLength;
15186
15187     if (timeRemaining > 0L && timeRemaining <= 10000L)
15188       nominalTickLength = 100L;
15189     else
15190       nominalTickLength = 1000L;
15191     nextTickLength = timeRemaining % nominalTickLength;
15192     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15193
15194     return nextTickLength;
15195 }
15196
15197 /* Adjust clock one minute up or down */
15198 void
15199 AdjustClock(Boolean which, int dir)
15200 {
15201     if(which) blackTimeRemaining += 60000*dir;
15202     else      whiteTimeRemaining += 60000*dir;
15203     DisplayBothClocks();
15204 }
15205
15206 /* Stop clocks and reset to a fresh time control */
15207 void
15208 ResetClocks()
15209 {
15210     (void) StopClockTimer();
15211     if (appData.icsActive) {
15212         whiteTimeRemaining = blackTimeRemaining = 0;
15213     } else if (searchTime) {
15214         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15215         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15216     } else { /* [HGM] correct new time quote for time odds */
15217         whiteTC = blackTC = fullTimeControlString;
15218         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15219         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15220     }
15221     if (whiteFlag || blackFlag) {
15222         DisplayTitle("");
15223         whiteFlag = blackFlag = FALSE;
15224     }
15225     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15226     DisplayBothClocks();
15227 }
15228
15229 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15230
15231 /* Decrement running clock by amount of time that has passed */
15232 void
15233 DecrementClocks()
15234 {
15235     long timeRemaining;
15236     long lastTickLength, fudge;
15237     TimeMark now;
15238
15239     if (!appData.clockMode) return;
15240     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15241
15242     GetTimeMark(&now);
15243
15244     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15245
15246     /* Fudge if we woke up a little too soon */
15247     fudge = intendedTickLength - lastTickLength;
15248     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15249
15250     if (WhiteOnMove(forwardMostMove)) {
15251         if(whiteNPS >= 0) lastTickLength = 0;
15252         timeRemaining = whiteTimeRemaining -= lastTickLength;
15253         if(timeRemaining < 0 && !appData.icsActive) {
15254             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15255             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15256                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15257                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15258             }
15259         }
15260         DisplayWhiteClock(whiteTimeRemaining - fudge,
15261                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15262     } else {
15263         if(blackNPS >= 0) lastTickLength = 0;
15264         timeRemaining = blackTimeRemaining -= lastTickLength;
15265         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15266             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15267             if(suddenDeath) {
15268                 blackStartMove = forwardMostMove;
15269                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15270             }
15271         }
15272         DisplayBlackClock(blackTimeRemaining - fudge,
15273                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15274     }
15275     if (CheckFlags()) return;
15276
15277     tickStartTM = now;
15278     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15279     StartClockTimer(intendedTickLength);
15280
15281     /* if the time remaining has fallen below the alarm threshold, sound the
15282      * alarm. if the alarm has sounded and (due to a takeback or time control
15283      * with increment) the time remaining has increased to a level above the
15284      * threshold, reset the alarm so it can sound again.
15285      */
15286
15287     if (appData.icsActive && appData.icsAlarm) {
15288
15289         /* make sure we are dealing with the user's clock */
15290         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15291                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15292            )) return;
15293
15294         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15295             alarmSounded = FALSE;
15296         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15297             PlayAlarmSound();
15298             alarmSounded = TRUE;
15299         }
15300     }
15301 }
15302
15303
15304 /* A player has just moved, so stop the previously running
15305    clock and (if in clock mode) start the other one.
15306    We redisplay both clocks in case we're in ICS mode, because
15307    ICS gives us an update to both clocks after every move.
15308    Note that this routine is called *after* forwardMostMove
15309    is updated, so the last fractional tick must be subtracted
15310    from the color that is *not* on move now.
15311 */
15312 void
15313 SwitchClocks(int newMoveNr)
15314 {
15315     long lastTickLength;
15316     TimeMark now;
15317     int flagged = FALSE;
15318
15319     GetTimeMark(&now);
15320
15321     if (StopClockTimer() && appData.clockMode) {
15322         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15323         if (!WhiteOnMove(forwardMostMove)) {
15324             if(blackNPS >= 0) lastTickLength = 0;
15325             blackTimeRemaining -= lastTickLength;
15326            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15327 //         if(pvInfoList[forwardMostMove].time == -1)
15328                  pvInfoList[forwardMostMove].time =               // use GUI time
15329                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15330         } else {
15331            if(whiteNPS >= 0) lastTickLength = 0;
15332            whiteTimeRemaining -= lastTickLength;
15333            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15334 //         if(pvInfoList[forwardMostMove].time == -1)
15335                  pvInfoList[forwardMostMove].time =
15336                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15337         }
15338         flagged = CheckFlags();
15339     }
15340     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15341     CheckTimeControl();
15342
15343     if (flagged || !appData.clockMode) return;
15344
15345     switch (gameMode) {
15346       case MachinePlaysBlack:
15347       case MachinePlaysWhite:
15348       case BeginningOfGame:
15349         if (pausing) return;
15350         break;
15351
15352       case EditGame:
15353       case PlayFromGameFile:
15354       case IcsExamining:
15355         return;
15356
15357       default:
15358         break;
15359     }
15360
15361     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15362         if(WhiteOnMove(forwardMostMove))
15363              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15364         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15365     }
15366
15367     tickStartTM = now;
15368     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15369       whiteTimeRemaining : blackTimeRemaining);
15370     StartClockTimer(intendedTickLength);
15371 }
15372
15373
15374 /* Stop both clocks */
15375 void
15376 StopClocks()
15377 {
15378     long lastTickLength;
15379     TimeMark now;
15380
15381     if (!StopClockTimer()) return;
15382     if (!appData.clockMode) return;
15383
15384     GetTimeMark(&now);
15385
15386     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15387     if (WhiteOnMove(forwardMostMove)) {
15388         if(whiteNPS >= 0) lastTickLength = 0;
15389         whiteTimeRemaining -= lastTickLength;
15390         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15391     } else {
15392         if(blackNPS >= 0) lastTickLength = 0;
15393         blackTimeRemaining -= lastTickLength;
15394         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15395     }
15396     CheckFlags();
15397 }
15398
15399 /* Start clock of player on move.  Time may have been reset, so
15400    if clock is already running, stop and restart it. */
15401 void
15402 StartClocks()
15403 {
15404     (void) StopClockTimer(); /* in case it was running already */
15405     DisplayBothClocks();
15406     if (CheckFlags()) return;
15407
15408     if (!appData.clockMode) return;
15409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15410
15411     GetTimeMark(&tickStartTM);
15412     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15413       whiteTimeRemaining : blackTimeRemaining);
15414
15415    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15416     whiteNPS = blackNPS = -1;
15417     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15418        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15419         whiteNPS = first.nps;
15420     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15421        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15422         blackNPS = first.nps;
15423     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15424         whiteNPS = second.nps;
15425     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15426         blackNPS = second.nps;
15427     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15428
15429     StartClockTimer(intendedTickLength);
15430 }
15431
15432 char *
15433 TimeString(ms)
15434      long ms;
15435 {
15436     long second, minute, hour, day;
15437     char *sign = "";
15438     static char buf[32];
15439
15440     if (ms > 0 && ms <= 9900) {
15441       /* convert milliseconds to tenths, rounding up */
15442       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15443
15444       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15445       return buf;
15446     }
15447
15448     /* convert milliseconds to seconds, rounding up */
15449     /* use floating point to avoid strangeness of integer division
15450        with negative dividends on many machines */
15451     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15452
15453     if (second < 0) {
15454         sign = "-";
15455         second = -second;
15456     }
15457
15458     day = second / (60 * 60 * 24);
15459     second = second % (60 * 60 * 24);
15460     hour = second / (60 * 60);
15461     second = second % (60 * 60);
15462     minute = second / 60;
15463     second = second % 60;
15464
15465     if (day > 0)
15466       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15467               sign, day, hour, minute, second);
15468     else if (hour > 0)
15469       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15470     else
15471       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15472
15473     return buf;
15474 }
15475
15476
15477 /*
15478  * This is necessary because some C libraries aren't ANSI C compliant yet.
15479  */
15480 char *
15481 StrStr(string, match)
15482      char *string, *match;
15483 {
15484     int i, length;
15485
15486     length = strlen(match);
15487
15488     for (i = strlen(string) - length; i >= 0; i--, string++)
15489       if (!strncmp(match, string, length))
15490         return string;
15491
15492     return NULL;
15493 }
15494
15495 char *
15496 StrCaseStr(string, match)
15497      char *string, *match;
15498 {
15499     int i, j, length;
15500
15501     length = strlen(match);
15502
15503     for (i = strlen(string) - length; i >= 0; i--, string++) {
15504         for (j = 0; j < length; j++) {
15505             if (ToLower(match[j]) != ToLower(string[j]))
15506               break;
15507         }
15508         if (j == length) return string;
15509     }
15510
15511     return NULL;
15512 }
15513
15514 #ifndef _amigados
15515 int
15516 StrCaseCmp(s1, s2)
15517      char *s1, *s2;
15518 {
15519     char c1, c2;
15520
15521     for (;;) {
15522         c1 = ToLower(*s1++);
15523         c2 = ToLower(*s2++);
15524         if (c1 > c2) return 1;
15525         if (c1 < c2) return -1;
15526         if (c1 == NULLCHAR) return 0;
15527     }
15528 }
15529
15530
15531 int
15532 ToLower(c)
15533      int c;
15534 {
15535     return isupper(c) ? tolower(c) : c;
15536 }
15537
15538
15539 int
15540 ToUpper(c)
15541      int c;
15542 {
15543     return islower(c) ? toupper(c) : c;
15544 }
15545 #endif /* !_amigados    */
15546
15547 char *
15548 StrSave(s)
15549      char *s;
15550 {
15551   char *ret;
15552
15553   if ((ret = (char *) malloc(strlen(s) + 1)))
15554     {
15555       safeStrCpy(ret, s, strlen(s)+1);
15556     }
15557   return ret;
15558 }
15559
15560 char *
15561 StrSavePtr(s, savePtr)
15562      char *s, **savePtr;
15563 {
15564     if (*savePtr) {
15565         free(*savePtr);
15566     }
15567     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15568       safeStrCpy(*savePtr, s, strlen(s)+1);
15569     }
15570     return(*savePtr);
15571 }
15572
15573 char *
15574 PGNDate()
15575 {
15576     time_t clock;
15577     struct tm *tm;
15578     char buf[MSG_SIZ];
15579
15580     clock = time((time_t *)NULL);
15581     tm = localtime(&clock);
15582     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15583             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15584     return StrSave(buf);
15585 }
15586
15587
15588 char *
15589 PositionToFEN(move, overrideCastling)
15590      int move;
15591      char *overrideCastling;
15592 {
15593     int i, j, fromX, fromY, toX, toY;
15594     int whiteToPlay;
15595     char buf[128];
15596     char *p, *q;
15597     int emptycount;
15598     ChessSquare piece;
15599
15600     whiteToPlay = (gameMode == EditPosition) ?
15601       !blackPlaysFirst : (move % 2 == 0);
15602     p = buf;
15603
15604     /* Piece placement data */
15605     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15606         emptycount = 0;
15607         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15608             if (boards[move][i][j] == EmptySquare) {
15609                 emptycount++;
15610             } else { ChessSquare piece = boards[move][i][j];
15611                 if (emptycount > 0) {
15612                     if(emptycount<10) /* [HGM] can be >= 10 */
15613                         *p++ = '0' + emptycount;
15614                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15615                     emptycount = 0;
15616                 }
15617                 if(PieceToChar(piece) == '+') {
15618                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15619                     *p++ = '+';
15620                     piece = (ChessSquare)(DEMOTED piece);
15621                 }
15622                 *p++ = PieceToChar(piece);
15623                 if(p[-1] == '~') {
15624                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15625                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15626                     *p++ = '~';
15627                 }
15628             }
15629         }
15630         if (emptycount > 0) {
15631             if(emptycount<10) /* [HGM] can be >= 10 */
15632                 *p++ = '0' + emptycount;
15633             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15634             emptycount = 0;
15635         }
15636         *p++ = '/';
15637     }
15638     *(p - 1) = ' ';
15639
15640     /* [HGM] print Crazyhouse or Shogi holdings */
15641     if( gameInfo.holdingsWidth ) {
15642         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15643         q = p;
15644         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15645             piece = boards[move][i][BOARD_WIDTH-1];
15646             if( piece != EmptySquare )
15647               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15648                   *p++ = PieceToChar(piece);
15649         }
15650         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15651             piece = boards[move][BOARD_HEIGHT-i-1][0];
15652             if( piece != EmptySquare )
15653               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15654                   *p++ = PieceToChar(piece);
15655         }
15656
15657         if( q == p ) *p++ = '-';
15658         *p++ = ']';
15659         *p++ = ' ';
15660     }
15661
15662     /* Active color */
15663     *p++ = whiteToPlay ? 'w' : 'b';
15664     *p++ = ' ';
15665
15666   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15667     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15668   } else {
15669   if(nrCastlingRights) {
15670      q = p;
15671      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15672        /* [HGM] write directly from rights */
15673            if(boards[move][CASTLING][2] != NoRights &&
15674               boards[move][CASTLING][0] != NoRights   )
15675                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15676            if(boards[move][CASTLING][2] != NoRights &&
15677               boards[move][CASTLING][1] != NoRights   )
15678                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15679            if(boards[move][CASTLING][5] != NoRights &&
15680               boards[move][CASTLING][3] != NoRights   )
15681                 *p++ = boards[move][CASTLING][3] + AAA;
15682            if(boards[move][CASTLING][5] != NoRights &&
15683               boards[move][CASTLING][4] != NoRights   )
15684                 *p++ = boards[move][CASTLING][4] + AAA;
15685      } else {
15686
15687         /* [HGM] write true castling rights */
15688         if( nrCastlingRights == 6 ) {
15689             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15690                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15691             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15692                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15693             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15694                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15695             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15696                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15697         }
15698      }
15699      if (q == p) *p++ = '-'; /* No castling rights */
15700      *p++ = ' ';
15701   }
15702
15703   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15704      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15705     /* En passant target square */
15706     if (move > backwardMostMove) {
15707         fromX = moveList[move - 1][0] - AAA;
15708         fromY = moveList[move - 1][1] - ONE;
15709         toX = moveList[move - 1][2] - AAA;
15710         toY = moveList[move - 1][3] - ONE;
15711         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15712             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15713             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15714             fromX == toX) {
15715             /* 2-square pawn move just happened */
15716             *p++ = toX + AAA;
15717             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15718         } else {
15719             *p++ = '-';
15720         }
15721     } else if(move == backwardMostMove) {
15722         // [HGM] perhaps we should always do it like this, and forget the above?
15723         if((signed char)boards[move][EP_STATUS] >= 0) {
15724             *p++ = boards[move][EP_STATUS] + AAA;
15725             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15726         } else {
15727             *p++ = '-';
15728         }
15729     } else {
15730         *p++ = '-';
15731     }
15732     *p++ = ' ';
15733   }
15734   }
15735
15736     /* [HGM] find reversible plies */
15737     {   int i = 0, j=move;
15738
15739         if (appData.debugMode) { int k;
15740             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15741             for(k=backwardMostMove; k<=forwardMostMove; k++)
15742                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15743
15744         }
15745
15746         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15747         if( j == backwardMostMove ) i += initialRulePlies;
15748         sprintf(p, "%d ", i);
15749         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15750     }
15751     /* Fullmove number */
15752     sprintf(p, "%d", (move / 2) + 1);
15753
15754     return StrSave(buf);
15755 }
15756
15757 Boolean
15758 ParseFEN(board, blackPlaysFirst, fen)
15759     Board board;
15760      int *blackPlaysFirst;
15761      char *fen;
15762 {
15763     int i, j;
15764     char *p, c;
15765     int emptycount;
15766     ChessSquare piece;
15767
15768     p = fen;
15769
15770     /* [HGM] by default clear Crazyhouse holdings, if present */
15771     if(gameInfo.holdingsWidth) {
15772        for(i=0; i<BOARD_HEIGHT; i++) {
15773            board[i][0]             = EmptySquare; /* black holdings */
15774            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15775            board[i][1]             = (ChessSquare) 0; /* black counts */
15776            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15777        }
15778     }
15779
15780     /* Piece placement data */
15781     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15782         j = 0;
15783         for (;;) {
15784             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15785                 if (*p == '/') p++;
15786                 emptycount = gameInfo.boardWidth - j;
15787                 while (emptycount--)
15788                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15789                 break;
15790 #if(BOARD_FILES >= 10)
15791             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15792                 p++; emptycount=10;
15793                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15794                 while (emptycount--)
15795                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15796 #endif
15797             } else if (isdigit(*p)) {
15798                 emptycount = *p++ - '0';
15799                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15800                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15801                 while (emptycount--)
15802                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15803             } else if (*p == '+' || isalpha(*p)) {
15804                 if (j >= gameInfo.boardWidth) return FALSE;
15805                 if(*p=='+') {
15806                     piece = CharToPiece(*++p);
15807                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15808                     piece = (ChessSquare) (PROMOTED piece ); p++;
15809                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15810                 } else piece = CharToPiece(*p++);
15811
15812                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15813                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15814                     piece = (ChessSquare) (PROMOTED piece);
15815                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15816                     p++;
15817                 }
15818                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15819             } else {
15820                 return FALSE;
15821             }
15822         }
15823     }
15824     while (*p == '/' || *p == ' ') p++;
15825
15826     /* [HGM] look for Crazyhouse holdings here */
15827     while(*p==' ') p++;
15828     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15829         if(*p == '[') p++;
15830         if(*p == '-' ) p++; /* empty holdings */ else {
15831             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15832             /* if we would allow FEN reading to set board size, we would   */
15833             /* have to add holdings and shift the board read so far here   */
15834             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15835                 p++;
15836                 if((int) piece >= (int) BlackPawn ) {
15837                     i = (int)piece - (int)BlackPawn;
15838                     i = PieceToNumber((ChessSquare)i);
15839                     if( i >= gameInfo.holdingsSize ) return FALSE;
15840                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15841                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15842                 } else {
15843                     i = (int)piece - (int)WhitePawn;
15844                     i = PieceToNumber((ChessSquare)i);
15845                     if( i >= gameInfo.holdingsSize ) return FALSE;
15846                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15847                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15848                 }
15849             }
15850         }
15851         if(*p == ']') p++;
15852     }
15853
15854     while(*p == ' ') p++;
15855
15856     /* Active color */
15857     c = *p++;
15858     if(appData.colorNickNames) {
15859       if( c == appData.colorNickNames[0] ) c = 'w'; else
15860       if( c == appData.colorNickNames[1] ) c = 'b';
15861     }
15862     switch (c) {
15863       case 'w':
15864         *blackPlaysFirst = FALSE;
15865         break;
15866       case 'b':
15867         *blackPlaysFirst = TRUE;
15868         break;
15869       default:
15870         return FALSE;
15871     }
15872
15873     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15874     /* return the extra info in global variiables             */
15875
15876     /* set defaults in case FEN is incomplete */
15877     board[EP_STATUS] = EP_UNKNOWN;
15878     for(i=0; i<nrCastlingRights; i++ ) {
15879         board[CASTLING][i] =
15880             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15881     }   /* assume possible unless obviously impossible */
15882     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15883     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15884     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15885                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15886     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15887     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15888     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15889                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15890     FENrulePlies = 0;
15891
15892     while(*p==' ') p++;
15893     if(nrCastlingRights) {
15894       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15895           /* castling indicator present, so default becomes no castlings */
15896           for(i=0; i<nrCastlingRights; i++ ) {
15897                  board[CASTLING][i] = NoRights;
15898           }
15899       }
15900       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15901              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15902              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15903              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15904         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15905
15906         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15907             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15908             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15909         }
15910         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15911             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15912         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15913                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15914         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15915                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15916         switch(c) {
15917           case'K':
15918               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15919               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15920               board[CASTLING][2] = whiteKingFile;
15921               break;
15922           case'Q':
15923               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15924               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15925               board[CASTLING][2] = whiteKingFile;
15926               break;
15927           case'k':
15928               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15929               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15930               board[CASTLING][5] = blackKingFile;
15931               break;
15932           case'q':
15933               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15934               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15935               board[CASTLING][5] = blackKingFile;
15936           case '-':
15937               break;
15938           default: /* FRC castlings */
15939               if(c >= 'a') { /* black rights */
15940                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15941                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15942                   if(i == BOARD_RGHT) break;
15943                   board[CASTLING][5] = i;
15944                   c -= AAA;
15945                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15946                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15947                   if(c > i)
15948                       board[CASTLING][3] = c;
15949                   else
15950                       board[CASTLING][4] = c;
15951               } else { /* white rights */
15952                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15953                     if(board[0][i] == WhiteKing) break;
15954                   if(i == BOARD_RGHT) break;
15955                   board[CASTLING][2] = i;
15956                   c -= AAA - 'a' + 'A';
15957                   if(board[0][c] >= WhiteKing) break;
15958                   if(c > i)
15959                       board[CASTLING][0] = c;
15960                   else
15961                       board[CASTLING][1] = c;
15962               }
15963         }
15964       }
15965       for(i=0; i<nrCastlingRights; i++)
15966         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15967     if (appData.debugMode) {
15968         fprintf(debugFP, "FEN castling rights:");
15969         for(i=0; i<nrCastlingRights; i++)
15970         fprintf(debugFP, " %d", board[CASTLING][i]);
15971         fprintf(debugFP, "\n");
15972     }
15973
15974       while(*p==' ') p++;
15975     }
15976
15977     /* read e.p. field in games that know e.p. capture */
15978     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15979        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15980       if(*p=='-') {
15981         p++; board[EP_STATUS] = EP_NONE;
15982       } else {
15983          char c = *p++ - AAA;
15984
15985          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15986          if(*p >= '0' && *p <='9') p++;
15987          board[EP_STATUS] = c;
15988       }
15989     }
15990
15991
15992     if(sscanf(p, "%d", &i) == 1) {
15993         FENrulePlies = i; /* 50-move ply counter */
15994         /* (The move number is still ignored)    */
15995     }
15996
15997     return TRUE;
15998 }
15999
16000 void
16001 EditPositionPasteFEN(char *fen)
16002 {
16003   if (fen != NULL) {
16004     Board initial_position;
16005
16006     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16007       DisplayError(_("Bad FEN position in clipboard"), 0);
16008       return ;
16009     } else {
16010       int savedBlackPlaysFirst = blackPlaysFirst;
16011       EditPositionEvent();
16012       blackPlaysFirst = savedBlackPlaysFirst;
16013       CopyBoard(boards[0], initial_position);
16014       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16015       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16016       DisplayBothClocks();
16017       DrawPosition(FALSE, boards[currentMove]);
16018     }
16019   }
16020 }
16021
16022 static char cseq[12] = "\\   ";
16023
16024 Boolean set_cont_sequence(char *new_seq)
16025 {
16026     int len;
16027     Boolean ret;
16028
16029     // handle bad attempts to set the sequence
16030         if (!new_seq)
16031                 return 0; // acceptable error - no debug
16032
16033     len = strlen(new_seq);
16034     ret = (len > 0) && (len < sizeof(cseq));
16035     if (ret)
16036       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16037     else if (appData.debugMode)
16038       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16039     return ret;
16040 }
16041
16042 /*
16043     reformat a source message so words don't cross the width boundary.  internal
16044     newlines are not removed.  returns the wrapped size (no null character unless
16045     included in source message).  If dest is NULL, only calculate the size required
16046     for the dest buffer.  lp argument indicats line position upon entry, and it's
16047     passed back upon exit.
16048 */
16049 int wrap(char *dest, char *src, int count, int width, int *lp)
16050 {
16051     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16052
16053     cseq_len = strlen(cseq);
16054     old_line = line = *lp;
16055     ansi = len = clen = 0;
16056
16057     for (i=0; i < count; i++)
16058     {
16059         if (src[i] == '\033')
16060             ansi = 1;
16061
16062         // if we hit the width, back up
16063         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16064         {
16065             // store i & len in case the word is too long
16066             old_i = i, old_len = len;
16067
16068             // find the end of the last word
16069             while (i && src[i] != ' ' && src[i] != '\n')
16070             {
16071                 i--;
16072                 len--;
16073             }
16074
16075             // word too long?  restore i & len before splitting it
16076             if ((old_i-i+clen) >= width)
16077             {
16078                 i = old_i;
16079                 len = old_len;
16080             }
16081
16082             // extra space?
16083             if (i && src[i-1] == ' ')
16084                 len--;
16085
16086             if (src[i] != ' ' && src[i] != '\n')
16087             {
16088                 i--;
16089                 if (len)
16090                     len--;
16091             }
16092
16093             // now append the newline and continuation sequence
16094             if (dest)
16095                 dest[len] = '\n';
16096             len++;
16097             if (dest)
16098                 strncpy(dest+len, cseq, cseq_len);
16099             len += cseq_len;
16100             line = cseq_len;
16101             clen = cseq_len;
16102             continue;
16103         }
16104
16105         if (dest)
16106             dest[len] = src[i];
16107         len++;
16108         if (!ansi)
16109             line++;
16110         if (src[i] == '\n')
16111             line = 0;
16112         if (src[i] == 'm')
16113             ansi = 0;
16114     }
16115     if (dest && appData.debugMode)
16116     {
16117         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16118             count, width, line, len, *lp);
16119         show_bytes(debugFP, src, count);
16120         fprintf(debugFP, "\ndest: ");
16121         show_bytes(debugFP, dest, len);
16122         fprintf(debugFP, "\n");
16123     }
16124     *lp = dest ? line : old_line;
16125
16126     return len;
16127 }
16128
16129 // [HGM] vari: routines for shelving variations
16130
16131 void
16132 PushInner(int firstMove, int lastMove)
16133 {
16134         int i, j, nrMoves = lastMove - firstMove;
16135
16136         // push current tail of game on stack
16137         savedResult[storedGames] = gameInfo.result;
16138         savedDetails[storedGames] = gameInfo.resultDetails;
16139         gameInfo.resultDetails = NULL;
16140         savedFirst[storedGames] = firstMove;
16141         savedLast [storedGames] = lastMove;
16142         savedFramePtr[storedGames] = framePtr;
16143         framePtr -= nrMoves; // reserve space for the boards
16144         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16145             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16146             for(j=0; j<MOVE_LEN; j++)
16147                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16148             for(j=0; j<2*MOVE_LEN; j++)
16149                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16150             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16151             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16152             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16153             pvInfoList[firstMove+i-1].depth = 0;
16154             commentList[framePtr+i] = commentList[firstMove+i];
16155             commentList[firstMove+i] = NULL;
16156         }
16157
16158         storedGames++;
16159         forwardMostMove = firstMove; // truncate game so we can start variation
16160 }
16161
16162 void
16163 PushTail(int firstMove, int lastMove)
16164 {
16165         if(appData.icsActive) { // only in local mode
16166                 forwardMostMove = currentMove; // mimic old ICS behavior
16167                 return;
16168         }
16169         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16170
16171         PushInner(firstMove, lastMove);
16172         if(storedGames == 1) GreyRevert(FALSE);
16173 }
16174
16175 void
16176 PopInner(Boolean annotate)
16177 {
16178         int i, j, nrMoves;
16179         char buf[8000], moveBuf[20];
16180
16181         storedGames--;
16182         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16183         nrMoves = savedLast[storedGames] - currentMove;
16184         if(annotate) {
16185                 int cnt = 10;
16186                 if(!WhiteOnMove(currentMove))
16187                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16188                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16189                 for(i=currentMove; i<forwardMostMove; i++) {
16190                         if(WhiteOnMove(i))
16191                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16192                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16193                         strcat(buf, moveBuf);
16194                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16195                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16196                 }
16197                 strcat(buf, ")");
16198         }
16199         for(i=1; i<=nrMoves; i++) { // copy last variation back
16200             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16201             for(j=0; j<MOVE_LEN; j++)
16202                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16203             for(j=0; j<2*MOVE_LEN; j++)
16204                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16205             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16206             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16207             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16208             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16209             commentList[currentMove+i] = commentList[framePtr+i];
16210             commentList[framePtr+i] = NULL;
16211         }
16212         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16213         framePtr = savedFramePtr[storedGames];
16214         gameInfo.result = savedResult[storedGames];
16215         if(gameInfo.resultDetails != NULL) {
16216             free(gameInfo.resultDetails);
16217       }
16218         gameInfo.resultDetails = savedDetails[storedGames];
16219         forwardMostMove = currentMove + nrMoves;
16220 }
16221
16222 Boolean
16223 PopTail(Boolean annotate)
16224 {
16225         if(appData.icsActive) return FALSE; // only in local mode
16226         if(!storedGames) return FALSE; // sanity
16227         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16228
16229         PopInner(annotate);
16230
16231         if(storedGames == 0) GreyRevert(TRUE);
16232         return TRUE;
16233 }
16234
16235 void
16236 CleanupTail()
16237 {       // remove all shelved variations
16238         int i;
16239         for(i=0; i<storedGames; i++) {
16240             if(savedDetails[i])
16241                 free(savedDetails[i]);
16242             savedDetails[i] = NULL;
16243         }
16244         for(i=framePtr; i<MAX_MOVES; i++) {
16245                 if(commentList[i]) free(commentList[i]);
16246                 commentList[i] = NULL;
16247         }
16248         framePtr = MAX_MOVES-1;
16249         storedGames = 0;
16250 }
16251
16252 void
16253 LoadVariation(int index, char *text)
16254 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16255         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16256         int level = 0, move;
16257
16258         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16259         // first find outermost bracketing variation
16260         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16261             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16262                 if(*p == '{') wait = '}'; else
16263                 if(*p == '[') wait = ']'; else
16264                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16265                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16266             }
16267             if(*p == wait) wait = NULLCHAR; // closing ]} found
16268             p++;
16269         }
16270         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16271         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16272         end[1] = NULLCHAR; // clip off comment beyond variation
16273         ToNrEvent(currentMove-1);
16274         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16275         // kludge: use ParsePV() to append variation to game
16276         move = currentMove;
16277         ParsePV(start, TRUE, TRUE);
16278         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16279         ClearPremoveHighlights();
16280         CommentPopDown();
16281         ToNrEvent(currentMove+1);
16282 }
16283