Allow changing MultiPV setting from Engine-Output window
[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     moveList[endPV-1][0] = fromX + AAA;
5297     moveList[endPV-1][1] = fromY + ONE;
5298     moveList[endPV-1][2] = toX + AAA;
5299     moveList[endPV-1][3] = toY + ONE;
5300     moveList[endPV-1][4] = promoChar;
5301     moveList[endPV-1][5] = NULLCHAR;
5302     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5303     if(storeComments)
5304         CoordsToAlgebraic(boards[endPV - 1],
5305                              PosFlags(endPV - 1),
5306                              fromY, fromX, toY, toX, promoChar,
5307                              parseList[endPV - 1]);
5308     else
5309         parseList[endPV-1][0] = NULLCHAR;
5310   } while(valid);
5311   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5312   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5313   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5314                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5315   DrawPosition(TRUE, boards[currentMove]);
5316 }
5317
5318 int
5319 MultiPV(ChessProgramState *cps)
5320 {       // check if engine supports MultiPV, and if so, return the nmber of the option that sets it
5321         int i;
5322         for(i=0; i<cps->nrOptions; i++)
5323             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5324                 return i;
5325         return -1;
5326 }
5327
5328 Boolean
5329 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5330 {
5331         int startPV, multi, lineStart, origIndex = index;
5332         char *p, buf2[MSG_SIZ];
5333
5334         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5335         lastX = x; lastY = y;
5336         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5337         lineStart = startPV = index;
5338         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5339         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5340         index = startPV;
5341         do{ while(buf[index] && buf[index] != '\n') index++;
5342         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5343         buf[index] = 0;
5344         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5345                 int n = first.option[multi].value;
5346                 if(origIndex < 10) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5347                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5348                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5349                 first.option[multi].value = n;
5350                 *start = *end = 0;
5351                 return TRUE;
5352         }
5353         ParsePV(buf+startPV, FALSE, !shiftKey);
5354         *start = startPV; *end = index-1;
5355         return TRUE;
5356 }
5357
5358 Boolean
5359 LoadPV(int x, int y)
5360 { // called on right mouse click to load PV
5361   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5362   lastX = x; lastY = y;
5363   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5364   return TRUE;
5365 }
5366
5367 void
5368 UnLoadPV()
5369 {
5370   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5371   if(endPV < 0) return;
5372   endPV = -1;
5373   if(shiftKey && gameMode == AnalyzeMode) {
5374         if(pushed) storedGames--; // abandon shelved tail of original game
5375         pushed = FALSE;
5376         forwardMostMove = currentMove;
5377         currentMove = oldFMM;
5378         ToNrEvent(forwardMostMove);
5379   }
5380   currentMove = forwardMostMove;
5381   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5382   ClearPremoveHighlights();
5383   DrawPosition(TRUE, boards[currentMove]);
5384 }
5385
5386 void
5387 MovePV(int x, int y, int h)
5388 { // step through PV based on mouse coordinates (called on mouse move)
5389   int margin = h>>3, step = 0;
5390
5391   // we must somehow check if right button is still down (might be released off board!)
5392   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5393   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5394   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5395   if(!step) return;
5396   lastX = x; lastY = y;
5397
5398   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5399   if(endPV < 0) return;
5400   if(y < margin) step = 1; else
5401   if(y > h - margin) step = -1;
5402   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5403   currentMove += step;
5404   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5405   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5406                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5407   DrawPosition(FALSE, boards[currentMove]);
5408 }
5409
5410
5411 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5412 // All positions will have equal probability, but the current method will not provide a unique
5413 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5414 #define DARK 1
5415 #define LITE 2
5416 #define ANY 3
5417
5418 int squaresLeft[4];
5419 int piecesLeft[(int)BlackPawn];
5420 int seed, nrOfShuffles;
5421
5422 void GetPositionNumber()
5423 {       // sets global variable seed
5424         int i;
5425
5426         seed = appData.defaultFrcPosition;
5427         if(seed < 0) { // randomize based on time for negative FRC position numbers
5428                 for(i=0; i<50; i++) seed += random();
5429                 seed = random() ^ random() >> 8 ^ random() << 8;
5430                 if(seed<0) seed = -seed;
5431         }
5432 }
5433
5434 int put(Board board, int pieceType, int rank, int n, int shade)
5435 // put the piece on the (n-1)-th empty squares of the given shade
5436 {
5437         int i;
5438
5439         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5440                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5441                         board[rank][i] = (ChessSquare) pieceType;
5442                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5443                         squaresLeft[ANY]--;
5444                         piecesLeft[pieceType]--;
5445                         return i;
5446                 }
5447         }
5448         return -1;
5449 }
5450
5451
5452 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5453 // calculate where the next piece goes, (any empty square), and put it there
5454 {
5455         int i;
5456
5457         i = seed % squaresLeft[shade];
5458         nrOfShuffles *= squaresLeft[shade];
5459         seed /= squaresLeft[shade];
5460         put(board, pieceType, rank, i, shade);
5461 }
5462
5463 void AddTwoPieces(Board board, int pieceType, int rank)
5464 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5465 {
5466         int i, n=squaresLeft[ANY], j=n-1, k;
5467
5468         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5469         i = seed % k;  // pick one
5470         nrOfShuffles *= k;
5471         seed /= k;
5472         while(i >= j) i -= j--;
5473         j = n - 1 - j; i += j;
5474         put(board, pieceType, rank, j, ANY);
5475         put(board, pieceType, rank, i, ANY);
5476 }
5477
5478 void SetUpShuffle(Board board, int number)
5479 {
5480         int i, p, first=1;
5481
5482         GetPositionNumber(); nrOfShuffles = 1;
5483
5484         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5485         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5486         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5487
5488         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5489
5490         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5491             p = (int) board[0][i];
5492             if(p < (int) BlackPawn) piecesLeft[p] ++;
5493             board[0][i] = EmptySquare;
5494         }
5495
5496         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5497             // shuffles restricted to allow normal castling put KRR first
5498             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5499                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5500             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5501                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5502             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5503                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5504             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5505                 put(board, WhiteRook, 0, 0, ANY);
5506             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5507         }
5508
5509         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5510             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5511             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5512                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5513                 while(piecesLeft[p] >= 2) {
5514                     AddOnePiece(board, p, 0, LITE);
5515                     AddOnePiece(board, p, 0, DARK);
5516                 }
5517                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5518             }
5519
5520         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5521             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5522             // but we leave King and Rooks for last, to possibly obey FRC restriction
5523             if(p == (int)WhiteRook) continue;
5524             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5525             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5526         }
5527
5528         // now everything is placed, except perhaps King (Unicorn) and Rooks
5529
5530         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5531             // Last King gets castling rights
5532             while(piecesLeft[(int)WhiteUnicorn]) {
5533                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5534                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5535             }
5536
5537             while(piecesLeft[(int)WhiteKing]) {
5538                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5539                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5540             }
5541
5542
5543         } else {
5544             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5545             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5546         }
5547
5548         // Only Rooks can be left; simply place them all
5549         while(piecesLeft[(int)WhiteRook]) {
5550                 i = put(board, WhiteRook, 0, 0, ANY);
5551                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5552                         if(first) {
5553                                 first=0;
5554                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5555                         }
5556                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5557                 }
5558         }
5559         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5560             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5561         }
5562
5563         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5564 }
5565
5566 int SetCharTable( char *table, const char * map )
5567 /* [HGM] moved here from winboard.c because of its general usefulness */
5568 /*       Basically a safe strcpy that uses the last character as King */
5569 {
5570     int result = FALSE; int NrPieces;
5571
5572     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5573                     && NrPieces >= 12 && !(NrPieces&1)) {
5574         int i; /* [HGM] Accept even length from 12 to 34 */
5575
5576         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5577         for( i=0; i<NrPieces/2-1; i++ ) {
5578             table[i] = map[i];
5579             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5580         }
5581         table[(int) WhiteKing]  = map[NrPieces/2-1];
5582         table[(int) BlackKing]  = map[NrPieces-1];
5583
5584         result = TRUE;
5585     }
5586
5587     return result;
5588 }
5589
5590 void Prelude(Board board)
5591 {       // [HGM] superchess: random selection of exo-pieces
5592         int i, j, k; ChessSquare p;
5593         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5594
5595         GetPositionNumber(); // use FRC position number
5596
5597         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5598             SetCharTable(pieceToChar, appData.pieceToCharTable);
5599             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5600                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5601         }
5602
5603         j = seed%4;                 seed /= 4;
5604         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5605         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5606         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5607         j = seed%3 + (seed%3 >= j); seed /= 3;
5608         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5609         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5610         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5611         j = seed%3;                 seed /= 3;
5612         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5613         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5614         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5615         j = seed%2 + (seed%2 >= j); seed /= 2;
5616         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5617         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5618         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5619         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5620         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5621         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5622         put(board, exoPieces[0],    0, 0, ANY);
5623         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5624 }
5625
5626 void
5627 InitPosition(redraw)
5628      int redraw;
5629 {
5630     ChessSquare (* pieces)[BOARD_FILES];
5631     int i, j, pawnRow, overrule,
5632     oldx = gameInfo.boardWidth,
5633     oldy = gameInfo.boardHeight,
5634     oldh = gameInfo.holdingsWidth;
5635     static int oldv;
5636
5637     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5638
5639     /* [AS] Initialize pv info list [HGM] and game status */
5640     {
5641         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5642             pvInfoList[i].depth = 0;
5643             boards[i][EP_STATUS] = EP_NONE;
5644             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5645         }
5646
5647         initialRulePlies = 0; /* 50-move counter start */
5648
5649         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5650         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5651     }
5652
5653
5654     /* [HGM] logic here is completely changed. In stead of full positions */
5655     /* the initialized data only consist of the two backranks. The switch */
5656     /* selects which one we will use, which is than copied to the Board   */
5657     /* initialPosition, which for the rest is initialized by Pawns and    */
5658     /* empty squares. This initial position is then copied to boards[0],  */
5659     /* possibly after shuffling, so that it remains available.            */
5660
5661     gameInfo.holdingsWidth = 0; /* default board sizes */
5662     gameInfo.boardWidth    = 8;
5663     gameInfo.boardHeight   = 8;
5664     gameInfo.holdingsSize  = 0;
5665     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5666     for(i=0; i<BOARD_FILES-2; i++)
5667       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5668     initialPosition[EP_STATUS] = EP_NONE;
5669     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5670     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5671          SetCharTable(pieceNickName, appData.pieceNickNames);
5672     else SetCharTable(pieceNickName, "............");
5673     pieces = FIDEArray;
5674
5675     switch (gameInfo.variant) {
5676     case VariantFischeRandom:
5677       shuffleOpenings = TRUE;
5678     default:
5679       break;
5680     case VariantShatranj:
5681       pieces = ShatranjArray;
5682       nrCastlingRights = 0;
5683       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5684       break;
5685     case VariantMakruk:
5686       pieces = makrukArray;
5687       nrCastlingRights = 0;
5688       startedFromSetupPosition = TRUE;
5689       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5690       break;
5691     case VariantTwoKings:
5692       pieces = twoKingsArray;
5693       break;
5694     case VariantCapaRandom:
5695       shuffleOpenings = TRUE;
5696     case VariantCapablanca:
5697       pieces = CapablancaArray;
5698       gameInfo.boardWidth = 10;
5699       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5700       break;
5701     case VariantGothic:
5702       pieces = GothicArray;
5703       gameInfo.boardWidth = 10;
5704       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5705       break;
5706     case VariantSChess:
5707       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5708       gameInfo.holdingsSize = 7;
5709       break;
5710     case VariantJanus:
5711       pieces = JanusArray;
5712       gameInfo.boardWidth = 10;
5713       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5714       nrCastlingRights = 6;
5715         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5716         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5717         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5718         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5719         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5720         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5721       break;
5722     case VariantFalcon:
5723       pieces = FalconArray;
5724       gameInfo.boardWidth = 10;
5725       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5726       break;
5727     case VariantXiangqi:
5728       pieces = XiangqiArray;
5729       gameInfo.boardWidth  = 9;
5730       gameInfo.boardHeight = 10;
5731       nrCastlingRights = 0;
5732       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5733       break;
5734     case VariantShogi:
5735       pieces = ShogiArray;
5736       gameInfo.boardWidth  = 9;
5737       gameInfo.boardHeight = 9;
5738       gameInfo.holdingsSize = 7;
5739       nrCastlingRights = 0;
5740       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5741       break;
5742     case VariantCourier:
5743       pieces = CourierArray;
5744       gameInfo.boardWidth  = 12;
5745       nrCastlingRights = 0;
5746       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5747       break;
5748     case VariantKnightmate:
5749       pieces = KnightmateArray;
5750       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5751       break;
5752     case VariantSpartan:
5753       pieces = SpartanArray;
5754       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5755       break;
5756     case VariantFairy:
5757       pieces = fairyArray;
5758       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5759       break;
5760     case VariantGreat:
5761       pieces = GreatArray;
5762       gameInfo.boardWidth = 10;
5763       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5764       gameInfo.holdingsSize = 8;
5765       break;
5766     case VariantSuper:
5767       pieces = FIDEArray;
5768       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5769       gameInfo.holdingsSize = 8;
5770       startedFromSetupPosition = TRUE;
5771       break;
5772     case VariantCrazyhouse:
5773     case VariantBughouse:
5774       pieces = FIDEArray;
5775       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5776       gameInfo.holdingsSize = 5;
5777       break;
5778     case VariantWildCastle:
5779       pieces = FIDEArray;
5780       /* !!?shuffle with kings guaranteed to be on d or e file */
5781       shuffleOpenings = 1;
5782       break;
5783     case VariantNoCastle:
5784       pieces = FIDEArray;
5785       nrCastlingRights = 0;
5786       /* !!?unconstrained back-rank shuffle */
5787       shuffleOpenings = 1;
5788       break;
5789     }
5790
5791     overrule = 0;
5792     if(appData.NrFiles >= 0) {
5793         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5794         gameInfo.boardWidth = appData.NrFiles;
5795     }
5796     if(appData.NrRanks >= 0) {
5797         gameInfo.boardHeight = appData.NrRanks;
5798     }
5799     if(appData.holdingsSize >= 0) {
5800         i = appData.holdingsSize;
5801         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5802         gameInfo.holdingsSize = i;
5803     }
5804     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5805     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5806         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5807
5808     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5809     if(pawnRow < 1) pawnRow = 1;
5810     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5811
5812     /* User pieceToChar list overrules defaults */
5813     if(appData.pieceToCharTable != NULL)
5814         SetCharTable(pieceToChar, appData.pieceToCharTable);
5815
5816     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5817
5818         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5819             s = (ChessSquare) 0; /* account holding counts in guard band */
5820         for( i=0; i<BOARD_HEIGHT; i++ )
5821             initialPosition[i][j] = s;
5822
5823         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5824         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5825         initialPosition[pawnRow][j] = WhitePawn;
5826         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5827         if(gameInfo.variant == VariantXiangqi) {
5828             if(j&1) {
5829                 initialPosition[pawnRow][j] =
5830                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5831                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5832                    initialPosition[2][j] = WhiteCannon;
5833                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5834                 }
5835             }
5836         }
5837         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5838     }
5839     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5840
5841             j=BOARD_LEFT+1;
5842             initialPosition[1][j] = WhiteBishop;
5843             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5844             j=BOARD_RGHT-2;
5845             initialPosition[1][j] = WhiteRook;
5846             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5847     }
5848
5849     if( nrCastlingRights == -1) {
5850         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5851         /*       This sets default castling rights from none to normal corners   */
5852         /* Variants with other castling rights must set them themselves above    */
5853         nrCastlingRights = 6;
5854
5855         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5856         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5857         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5858         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5859         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5860         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5861      }
5862
5863      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5864      if(gameInfo.variant == VariantGreat) { // promotion commoners
5865         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5866         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5867         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5868         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5869      }
5870      if( gameInfo.variant == VariantSChess ) {
5871       initialPosition[1][0] = BlackMarshall;
5872       initialPosition[2][0] = BlackAngel;
5873       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5874       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5875       initialPosition[1][1] = initialPosition[2][1] = 
5876       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5877      }
5878   if (appData.debugMode) {
5879     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5880   }
5881     if(shuffleOpenings) {
5882         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5883         startedFromSetupPosition = TRUE;
5884     }
5885     if(startedFromPositionFile) {
5886       /* [HGM] loadPos: use PositionFile for every new game */
5887       CopyBoard(initialPosition, filePosition);
5888       for(i=0; i<nrCastlingRights; i++)
5889           initialRights[i] = filePosition[CASTLING][i];
5890       startedFromSetupPosition = TRUE;
5891     }
5892
5893     CopyBoard(boards[0], initialPosition);
5894
5895     if(oldx != gameInfo.boardWidth ||
5896        oldy != gameInfo.boardHeight ||
5897        oldv != gameInfo.variant ||
5898        oldh != gameInfo.holdingsWidth
5899                                          )
5900             InitDrawingSizes(-2 ,0);
5901
5902     oldv = gameInfo.variant;
5903     if (redraw)
5904       DrawPosition(TRUE, boards[currentMove]);
5905 }
5906
5907 void
5908 SendBoard(cps, moveNum)
5909      ChessProgramState *cps;
5910      int moveNum;
5911 {
5912     char message[MSG_SIZ];
5913
5914     if (cps->useSetboard) {
5915       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5916       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5917       SendToProgram(message, cps);
5918       free(fen);
5919
5920     } else {
5921       ChessSquare *bp;
5922       int i, j;
5923       /* Kludge to set black to move, avoiding the troublesome and now
5924        * deprecated "black" command.
5925        */
5926       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5927         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5928
5929       SendToProgram("edit\n", cps);
5930       SendToProgram("#\n", cps);
5931       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5932         bp = &boards[moveNum][i][BOARD_LEFT];
5933         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5934           if ((int) *bp < (int) BlackPawn) {
5935             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5936                     AAA + j, ONE + i);
5937             if(message[0] == '+' || message[0] == '~') {
5938               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5939                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5940                         AAA + j, ONE + i);
5941             }
5942             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5943                 message[1] = BOARD_RGHT   - 1 - j + '1';
5944                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5945             }
5946             SendToProgram(message, cps);
5947           }
5948         }
5949       }
5950
5951       SendToProgram("c\n", cps);
5952       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5953         bp = &boards[moveNum][i][BOARD_LEFT];
5954         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5955           if (((int) *bp != (int) EmptySquare)
5956               && ((int) *bp >= (int) BlackPawn)) {
5957             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5958                     AAA + j, ONE + i);
5959             if(message[0] == '+' || message[0] == '~') {
5960               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5961                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5962                         AAA + j, ONE + i);
5963             }
5964             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5965                 message[1] = BOARD_RGHT   - 1 - j + '1';
5966                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5967             }
5968             SendToProgram(message, cps);
5969           }
5970         }
5971       }
5972
5973       SendToProgram(".\n", cps);
5974     }
5975     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5976 }
5977
5978 ChessSquare
5979 DefaultPromoChoice(int white)
5980 {
5981     ChessSquare result;
5982     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5983         result = WhiteFerz; // no choice
5984     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5985         result= WhiteKing; // in Suicide Q is the last thing we want
5986     else if(gameInfo.variant == VariantSpartan)
5987         result = white ? WhiteQueen : WhiteAngel;
5988     else result = WhiteQueen;
5989     if(!white) result = WHITE_TO_BLACK result;
5990     return result;
5991 }
5992
5993 static int autoQueen; // [HGM] oneclick
5994
5995 int
5996 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5997 {
5998     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5999     /* [HGM] add Shogi promotions */
6000     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6001     ChessSquare piece;
6002     ChessMove moveType;
6003     Boolean premove;
6004
6005     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6006     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6007
6008     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6009       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6010         return FALSE;
6011
6012     piece = boards[currentMove][fromY][fromX];
6013     if(gameInfo.variant == VariantShogi) {
6014         promotionZoneSize = BOARD_HEIGHT/3;
6015         highestPromotingPiece = (int)WhiteFerz;
6016     } else if(gameInfo.variant == VariantMakruk) {
6017         promotionZoneSize = 3;
6018     }
6019
6020     // Treat Lance as Pawn when it is not representing Amazon
6021     if(gameInfo.variant != VariantSuper) {
6022         if(piece == WhiteLance) piece = WhitePawn; else
6023         if(piece == BlackLance) piece = BlackPawn;
6024     }
6025
6026     // next weed out all moves that do not touch the promotion zone at all
6027     if((int)piece >= BlackPawn) {
6028         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6029              return FALSE;
6030         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6031     } else {
6032         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6033            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6034     }
6035
6036     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6037
6038     // weed out mandatory Shogi promotions
6039     if(gameInfo.variant == VariantShogi) {
6040         if(piece >= BlackPawn) {
6041             if(toY == 0 && piece == BlackPawn ||
6042                toY == 0 && piece == BlackQueen ||
6043                toY <= 1 && piece == BlackKnight) {
6044                 *promoChoice = '+';
6045                 return FALSE;
6046             }
6047         } else {
6048             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6049                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6050                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6051                 *promoChoice = '+';
6052                 return FALSE;
6053             }
6054         }
6055     }
6056
6057     // weed out obviously illegal Pawn moves
6058     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6059         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6060         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6061         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6062         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6063         // note we are not allowed to test for valid (non-)capture, due to premove
6064     }
6065
6066     // we either have a choice what to promote to, or (in Shogi) whether to promote
6067     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6068         *promoChoice = PieceToChar(BlackFerz);  // no choice
6069         return FALSE;
6070     }
6071     // no sense asking what we must promote to if it is going to explode...
6072     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6073         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6074         return FALSE;
6075     }
6076     // give caller the default choice even if we will not make it
6077     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6078     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6079     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6080                            && gameInfo.variant != VariantShogi
6081                            && gameInfo.variant != VariantSuper) return FALSE;
6082     if(autoQueen) return FALSE; // predetermined
6083
6084     // suppress promotion popup on illegal moves that are not premoves
6085     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6086               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6087     if(appData.testLegality && !premove) {
6088         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6089                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6090         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6091             return FALSE;
6092     }
6093
6094     return TRUE;
6095 }
6096
6097 int
6098 InPalace(row, column)
6099      int row, column;
6100 {   /* [HGM] for Xiangqi */
6101     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6102          column < (BOARD_WIDTH + 4)/2 &&
6103          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6104     return FALSE;
6105 }
6106
6107 int
6108 PieceForSquare (x, y)
6109      int x;
6110      int y;
6111 {
6112   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6113      return -1;
6114   else
6115      return boards[currentMove][y][x];
6116 }
6117
6118 int
6119 OKToStartUserMove(x, y)
6120      int x, y;
6121 {
6122     ChessSquare from_piece;
6123     int white_piece;
6124
6125     if (matchMode) return FALSE;
6126     if (gameMode == EditPosition) return TRUE;
6127
6128     if (x >= 0 && y >= 0)
6129       from_piece = boards[currentMove][y][x];
6130     else
6131       from_piece = EmptySquare;
6132
6133     if (from_piece == EmptySquare) return FALSE;
6134
6135     white_piece = (int)from_piece >= (int)WhitePawn &&
6136       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6137
6138     switch (gameMode) {
6139       case PlayFromGameFile:
6140       case AnalyzeFile:
6141       case TwoMachinesPlay:
6142       case EndOfGame:
6143         return FALSE;
6144
6145       case IcsObserving:
6146       case IcsIdle:
6147         return FALSE;
6148
6149       case MachinePlaysWhite:
6150       case IcsPlayingBlack:
6151         if (appData.zippyPlay) return FALSE;
6152         if (white_piece) {
6153             DisplayMoveError(_("You are playing Black"));
6154             return FALSE;
6155         }
6156         break;
6157
6158       case MachinePlaysBlack:
6159       case IcsPlayingWhite:
6160         if (appData.zippyPlay) return FALSE;
6161         if (!white_piece) {
6162             DisplayMoveError(_("You are playing White"));
6163             return FALSE;
6164         }
6165         break;
6166
6167       case EditGame:
6168         if (!white_piece && WhiteOnMove(currentMove)) {
6169             DisplayMoveError(_("It is White's turn"));
6170             return FALSE;
6171         }
6172         if (white_piece && !WhiteOnMove(currentMove)) {
6173             DisplayMoveError(_("It is Black's turn"));
6174             return FALSE;
6175         }
6176         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6177             /* Editing correspondence game history */
6178             /* Could disallow this or prompt for confirmation */
6179             cmailOldMove = -1;
6180         }
6181         break;
6182
6183       case BeginningOfGame:
6184         if (appData.icsActive) return FALSE;
6185         if (!appData.noChessProgram) {
6186             if (!white_piece) {
6187                 DisplayMoveError(_("You are playing White"));
6188                 return FALSE;
6189             }
6190         }
6191         break;
6192
6193       case Training:
6194         if (!white_piece && WhiteOnMove(currentMove)) {
6195             DisplayMoveError(_("It is White's turn"));
6196             return FALSE;
6197         }
6198         if (white_piece && !WhiteOnMove(currentMove)) {
6199             DisplayMoveError(_("It is Black's turn"));
6200             return FALSE;
6201         }
6202         break;
6203
6204       default:
6205       case IcsExamining:
6206         break;
6207     }
6208     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6209         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6210         && gameMode != AnalyzeFile && gameMode != Training) {
6211         DisplayMoveError(_("Displayed position is not current"));
6212         return FALSE;
6213     }
6214     return TRUE;
6215 }
6216
6217 Boolean
6218 OnlyMove(int *x, int *y, Boolean captures) {
6219     DisambiguateClosure cl;
6220     if (appData.zippyPlay) return FALSE;
6221     switch(gameMode) {
6222       case MachinePlaysBlack:
6223       case IcsPlayingWhite:
6224       case BeginningOfGame:
6225         if(!WhiteOnMove(currentMove)) return FALSE;
6226         break;
6227       case MachinePlaysWhite:
6228       case IcsPlayingBlack:
6229         if(WhiteOnMove(currentMove)) return FALSE;
6230         break;
6231       case EditGame:
6232         break;
6233       default:
6234         return FALSE;
6235     }
6236     cl.pieceIn = EmptySquare;
6237     cl.rfIn = *y;
6238     cl.ffIn = *x;
6239     cl.rtIn = -1;
6240     cl.ftIn = -1;
6241     cl.promoCharIn = NULLCHAR;
6242     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6243     if( cl.kind == NormalMove ||
6244         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6245         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6246         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6247       fromX = cl.ff;
6248       fromY = cl.rf;
6249       *x = cl.ft;
6250       *y = cl.rt;
6251       return TRUE;
6252     }
6253     if(cl.kind != ImpossibleMove) return FALSE;
6254     cl.pieceIn = EmptySquare;
6255     cl.rfIn = -1;
6256     cl.ffIn = -1;
6257     cl.rtIn = *y;
6258     cl.ftIn = *x;
6259     cl.promoCharIn = NULLCHAR;
6260     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6261     if( cl.kind == NormalMove ||
6262         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6263         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6264         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6265       fromX = cl.ff;
6266       fromY = cl.rf;
6267       *x = cl.ft;
6268       *y = cl.rt;
6269       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6270       return TRUE;
6271     }
6272     return FALSE;
6273 }
6274
6275 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6276 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6277 int lastLoadGameUseList = FALSE;
6278 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6279 ChessMove lastLoadGameStart = EndOfFile;
6280
6281 void
6282 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6283      int fromX, fromY, toX, toY;
6284      int promoChar;
6285 {
6286     ChessMove moveType;
6287     ChessSquare pdown, pup;
6288
6289     /* Check if the user is playing in turn.  This is complicated because we
6290        let the user "pick up" a piece before it is his turn.  So the piece he
6291        tried to pick up may have been captured by the time he puts it down!
6292        Therefore we use the color the user is supposed to be playing in this
6293        test, not the color of the piece that is currently on the starting
6294        square---except in EditGame mode, where the user is playing both
6295        sides; fortunately there the capture race can't happen.  (It can
6296        now happen in IcsExamining mode, but that's just too bad.  The user
6297        will get a somewhat confusing message in that case.)
6298        */
6299
6300     switch (gameMode) {
6301       case PlayFromGameFile:
6302       case AnalyzeFile:
6303       case TwoMachinesPlay:
6304       case EndOfGame:
6305       case IcsObserving:
6306       case IcsIdle:
6307         /* We switched into a game mode where moves are not accepted,
6308            perhaps while the mouse button was down. */
6309         return;
6310
6311       case MachinePlaysWhite:
6312         /* User is moving for Black */
6313         if (WhiteOnMove(currentMove)) {
6314             DisplayMoveError(_("It is White's turn"));
6315             return;
6316         }
6317         break;
6318
6319       case MachinePlaysBlack:
6320         /* User is moving for White */
6321         if (!WhiteOnMove(currentMove)) {
6322             DisplayMoveError(_("It is Black's turn"));
6323             return;
6324         }
6325         break;
6326
6327       case EditGame:
6328       case IcsExamining:
6329       case BeginningOfGame:
6330       case AnalyzeMode:
6331       case Training:
6332         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6333         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6334             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6335             /* User is moving for Black */
6336             if (WhiteOnMove(currentMove)) {
6337                 DisplayMoveError(_("It is White's turn"));
6338                 return;
6339             }
6340         } else {
6341             /* User is moving for White */
6342             if (!WhiteOnMove(currentMove)) {
6343                 DisplayMoveError(_("It is Black's turn"));
6344                 return;
6345             }
6346         }
6347         break;
6348
6349       case IcsPlayingBlack:
6350         /* User is moving for Black */
6351         if (WhiteOnMove(currentMove)) {
6352             if (!appData.premove) {
6353                 DisplayMoveError(_("It is White's turn"));
6354             } else if (toX >= 0 && toY >= 0) {
6355                 premoveToX = toX;
6356                 premoveToY = toY;
6357                 premoveFromX = fromX;
6358                 premoveFromY = fromY;
6359                 premovePromoChar = promoChar;
6360                 gotPremove = 1;
6361                 if (appData.debugMode)
6362                     fprintf(debugFP, "Got premove: fromX %d,"
6363                             "fromY %d, toX %d, toY %d\n",
6364                             fromX, fromY, toX, toY);
6365             }
6366             return;
6367         }
6368         break;
6369
6370       case IcsPlayingWhite:
6371         /* User is moving for White */
6372         if (!WhiteOnMove(currentMove)) {
6373             if (!appData.premove) {
6374                 DisplayMoveError(_("It is Black's turn"));
6375             } else if (toX >= 0 && toY >= 0) {
6376                 premoveToX = toX;
6377                 premoveToY = toY;
6378                 premoveFromX = fromX;
6379                 premoveFromY = fromY;
6380                 premovePromoChar = promoChar;
6381                 gotPremove = 1;
6382                 if (appData.debugMode)
6383                     fprintf(debugFP, "Got premove: fromX %d,"
6384                             "fromY %d, toX %d, toY %d\n",
6385                             fromX, fromY, toX, toY);
6386             }
6387             return;
6388         }
6389         break;
6390
6391       default:
6392         break;
6393
6394       case EditPosition:
6395         /* EditPosition, empty square, or different color piece;
6396            click-click move is possible */
6397         if (toX == -2 || toY == -2) {
6398             boards[0][fromY][fromX] = EmptySquare;
6399             DrawPosition(FALSE, boards[currentMove]);
6400             return;
6401         } else if (toX >= 0 && toY >= 0) {
6402             boards[0][toY][toX] = boards[0][fromY][fromX];
6403             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6404                 if(boards[0][fromY][0] != EmptySquare) {
6405                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6406                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6407                 }
6408             } else
6409             if(fromX == BOARD_RGHT+1) {
6410                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6411                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6412                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6413                 }
6414             } else
6415             boards[0][fromY][fromX] = EmptySquare;
6416             DrawPosition(FALSE, boards[currentMove]);
6417             return;
6418         }
6419         return;
6420     }
6421
6422     if(toX < 0 || toY < 0) return;
6423     pdown = boards[currentMove][fromY][fromX];
6424     pup = boards[currentMove][toY][toX];
6425
6426     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6427     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6428          if( pup != EmptySquare ) return;
6429          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6430            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6431                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6432            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6433            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6434            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6435            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6436          fromY = DROP_RANK;
6437     }
6438
6439     /* [HGM] always test for legality, to get promotion info */
6440     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6441                                          fromY, fromX, toY, toX, promoChar);
6442     /* [HGM] but possibly ignore an IllegalMove result */
6443     if (appData.testLegality) {
6444         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6445             DisplayMoveError(_("Illegal move"));
6446             return;
6447         }
6448     }
6449
6450     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6451 }
6452
6453 /* Common tail of UserMoveEvent and DropMenuEvent */
6454 int
6455 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6456      ChessMove moveType;
6457      int fromX, fromY, toX, toY;
6458      /*char*/int promoChar;
6459 {
6460     char *bookHit = 0;
6461
6462     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6463         // [HGM] superchess: suppress promotions to non-available piece
6464         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6465         if(WhiteOnMove(currentMove)) {
6466             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6467         } else {
6468             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6469         }
6470     }
6471
6472     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6473        move type in caller when we know the move is a legal promotion */
6474     if(moveType == NormalMove && promoChar)
6475         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6476
6477     /* [HGM] <popupFix> The following if has been moved here from
6478        UserMoveEvent(). Because it seemed to belong here (why not allow
6479        piece drops in training games?), and because it can only be
6480        performed after it is known to what we promote. */
6481     if (gameMode == Training) {
6482       /* compare the move played on the board to the next move in the
6483        * game. If they match, display the move and the opponent's response.
6484        * If they don't match, display an error message.
6485        */
6486       int saveAnimate;
6487       Board testBoard;
6488       CopyBoard(testBoard, boards[currentMove]);
6489       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6490
6491       if (CompareBoards(testBoard, boards[currentMove+1])) {
6492         ForwardInner(currentMove+1);
6493
6494         /* Autoplay the opponent's response.
6495          * if appData.animate was TRUE when Training mode was entered,
6496          * the response will be animated.
6497          */
6498         saveAnimate = appData.animate;
6499         appData.animate = animateTraining;
6500         ForwardInner(currentMove+1);
6501         appData.animate = saveAnimate;
6502
6503         /* check for the end of the game */
6504         if (currentMove >= forwardMostMove) {
6505           gameMode = PlayFromGameFile;
6506           ModeHighlight();
6507           SetTrainingModeOff();
6508           DisplayInformation(_("End of game"));
6509         }
6510       } else {
6511         DisplayError(_("Incorrect move"), 0);
6512       }
6513       return 1;
6514     }
6515
6516   /* Ok, now we know that the move is good, so we can kill
6517      the previous line in Analysis Mode */
6518   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6519                                 && currentMove < forwardMostMove) {
6520     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6521     else forwardMostMove = currentMove;
6522   }
6523
6524   /* If we need the chess program but it's dead, restart it */
6525   ResurrectChessProgram();
6526
6527   /* A user move restarts a paused game*/
6528   if (pausing)
6529     PauseEvent();
6530
6531   thinkOutput[0] = NULLCHAR;
6532
6533   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6534
6535   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6536     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6537     return 1;
6538   }
6539
6540   if (gameMode == BeginningOfGame) {
6541     if (appData.noChessProgram) {
6542       gameMode = EditGame;
6543       SetGameInfo();
6544     } else {
6545       char buf[MSG_SIZ];
6546       gameMode = MachinePlaysBlack;
6547       StartClocks();
6548       SetGameInfo();
6549       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6550       DisplayTitle(buf);
6551       if (first.sendName) {
6552         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6553         SendToProgram(buf, &first);
6554       }
6555       StartClocks();
6556     }
6557     ModeHighlight();
6558   }
6559
6560   /* Relay move to ICS or chess engine */
6561   if (appData.icsActive) {
6562     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6563         gameMode == IcsExamining) {
6564       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6565         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6566         SendToICS("draw ");
6567         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6568       }
6569       // also send plain move, in case ICS does not understand atomic claims
6570       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6571       ics_user_moved = 1;
6572     }
6573   } else {
6574     if (first.sendTime && (gameMode == BeginningOfGame ||
6575                            gameMode == MachinePlaysWhite ||
6576                            gameMode == MachinePlaysBlack)) {
6577       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6578     }
6579     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6580          // [HGM] book: if program might be playing, let it use book
6581         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6582         first.maybeThinking = TRUE;
6583     } else SendMoveToProgram(forwardMostMove-1, &first);
6584     if (currentMove == cmailOldMove + 1) {
6585       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6586     }
6587   }
6588
6589   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6590
6591   switch (gameMode) {
6592   case EditGame:
6593     if(appData.testLegality)
6594     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6595     case MT_NONE:
6596     case MT_CHECK:
6597       break;
6598     case MT_CHECKMATE:
6599     case MT_STAINMATE:
6600       if (WhiteOnMove(currentMove)) {
6601         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6602       } else {
6603         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6604       }
6605       break;
6606     case MT_STALEMATE:
6607       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6608       break;
6609     }
6610     break;
6611
6612   case MachinePlaysBlack:
6613   case MachinePlaysWhite:
6614     /* disable certain menu options while machine is thinking */
6615     SetMachineThinkingEnables();
6616     break;
6617
6618   default:
6619     break;
6620   }
6621
6622   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6623   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6624
6625   if(bookHit) { // [HGM] book: simulate book reply
6626         static char bookMove[MSG_SIZ]; // a bit generous?
6627
6628         programStats.nodes = programStats.depth = programStats.time =
6629         programStats.score = programStats.got_only_move = 0;
6630         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6631
6632         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6633         strcat(bookMove, bookHit);
6634         HandleMachineMove(bookMove, &first);
6635   }
6636   return 1;
6637 }
6638
6639 void
6640 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6641      Board board;
6642      int flags;
6643      ChessMove kind;
6644      int rf, ff, rt, ft;
6645      VOIDSTAR closure;
6646 {
6647     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6648     Markers *m = (Markers *) closure;
6649     if(rf == fromY && ff == fromX)
6650         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6651                          || kind == WhiteCapturesEnPassant
6652                          || kind == BlackCapturesEnPassant);
6653     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6654 }
6655
6656 void
6657 MarkTargetSquares(int clear)
6658 {
6659   int x, y;
6660   if(!appData.markers || !appData.highlightDragging ||
6661      !appData.testLegality || gameMode == EditPosition) return;
6662   if(clear) {
6663     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6664   } else {
6665     int capt = 0;
6666     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6667     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6668       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6669       if(capt)
6670       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6671     }
6672   }
6673   DrawPosition(TRUE, NULL);
6674 }
6675
6676 int
6677 Explode(Board board, int fromX, int fromY, int toX, int toY)
6678 {
6679     if(gameInfo.variant == VariantAtomic &&
6680        (board[toY][toX] != EmptySquare ||                     // capture?
6681         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6682                          board[fromY][fromX] == BlackPawn   )
6683       )) {
6684         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6685         return TRUE;
6686     }
6687     return FALSE;
6688 }
6689
6690 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6691
6692 int CanPromote(ChessSquare piece, int y)
6693 {
6694         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6695         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6696         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6697            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6698            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6699                                                   gameInfo.variant == VariantMakruk) return FALSE;
6700         return (piece == BlackPawn && y == 1 ||
6701                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6702                 piece == BlackLance && y == 1 ||
6703                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6704 }
6705
6706 void LeftClick(ClickType clickType, int xPix, int yPix)
6707 {
6708     int x, y;
6709     Boolean saveAnimate;
6710     static int second = 0, promotionChoice = 0, clearFlag = 0;
6711     char promoChoice = NULLCHAR;
6712     ChessSquare piece;
6713
6714     if(appData.seekGraph && appData.icsActive && loggedOn &&
6715         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6716         SeekGraphClick(clickType, xPix, yPix, 0);
6717         return;
6718     }
6719
6720     if (clickType == Press) ErrorPopDown();
6721     MarkTargetSquares(1);
6722
6723     x = EventToSquare(xPix, BOARD_WIDTH);
6724     y = EventToSquare(yPix, BOARD_HEIGHT);
6725     if (!flipView && y >= 0) {
6726         y = BOARD_HEIGHT - 1 - y;
6727     }
6728     if (flipView && x >= 0) {
6729         x = BOARD_WIDTH - 1 - x;
6730     }
6731
6732     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6733         defaultPromoChoice = promoSweep;
6734         promoSweep = EmptySquare;   // terminate sweep
6735         promoDefaultAltered = TRUE;
6736         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6737     }
6738
6739     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6740         if(clickType == Release) return; // ignore upclick of click-click destination
6741         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6742         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6743         if(gameInfo.holdingsWidth &&
6744                 (WhiteOnMove(currentMove)
6745                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6746                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6747             // click in right holdings, for determining promotion piece
6748             ChessSquare p = boards[currentMove][y][x];
6749             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6750             if(p != EmptySquare) {
6751                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6752                 fromX = fromY = -1;
6753                 return;
6754             }
6755         }
6756         DrawPosition(FALSE, boards[currentMove]);
6757         return;
6758     }
6759
6760     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6761     if(clickType == Press
6762             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6763               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6764               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6765         return;
6766
6767     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6768         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6769
6770     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6771         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6772                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6773         defaultPromoChoice = DefaultPromoChoice(side);
6774     }
6775
6776     autoQueen = appData.alwaysPromoteToQueen;
6777
6778     if (fromX == -1) {
6779       int originalY = y;
6780       gatingPiece = EmptySquare;
6781       if (clickType != Press) {
6782         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6783             DragPieceEnd(xPix, yPix); dragging = 0;
6784             DrawPosition(FALSE, NULL);
6785         }
6786         return;
6787       }
6788       fromX = x; fromY = y;
6789       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6790          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6791          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6792             /* First square */
6793             if (OKToStartUserMove(fromX, fromY)) {
6794                 second = 0;
6795                 MarkTargetSquares(0);
6796                 DragPieceBegin(xPix, yPix); dragging = 1;
6797                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6798                     promoSweep = defaultPromoChoice;
6799                     selectFlag = 0; lastX = xPix; lastY = yPix;
6800                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6801                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6802                 }
6803                 if (appData.highlightDragging) {
6804                     SetHighlights(fromX, fromY, -1, -1);
6805                 }
6806             } else fromX = fromY = -1;
6807             return;
6808         }
6809     }
6810
6811     /* fromX != -1 */
6812     if (clickType == Press && gameMode != EditPosition) {
6813         ChessSquare fromP;
6814         ChessSquare toP;
6815         int frc;
6816
6817         // ignore off-board to clicks
6818         if(y < 0 || x < 0) return;
6819
6820         /* Check if clicking again on the same color piece */
6821         fromP = boards[currentMove][fromY][fromX];
6822         toP = boards[currentMove][y][x];
6823         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6824         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6825              WhitePawn <= toP && toP <= WhiteKing &&
6826              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6827              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6828             (BlackPawn <= fromP && fromP <= BlackKing &&
6829              BlackPawn <= toP && toP <= BlackKing &&
6830              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6831              !(fromP == BlackKing && toP == BlackRook && frc))) {
6832             /* Clicked again on same color piece -- changed his mind */
6833             second = (x == fromX && y == fromY);
6834             promoDefaultAltered = FALSE;
6835            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6836             if (appData.highlightDragging) {
6837                 SetHighlights(x, y, -1, -1);
6838             } else {
6839                 ClearHighlights();
6840             }
6841             if (OKToStartUserMove(x, y)) {
6842                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6843                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6844                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6845                  gatingPiece = boards[currentMove][fromY][fromX];
6846                 else gatingPiece = EmptySquare;
6847                 fromX = x;
6848                 fromY = y; dragging = 1;
6849                 MarkTargetSquares(0);
6850                 DragPieceBegin(xPix, yPix);
6851                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6852                     promoSweep = defaultPromoChoice;
6853                     selectFlag = 0; lastX = xPix; lastY = yPix;
6854                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6855                 }
6856             }
6857            }
6858            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6859            second = FALSE; 
6860         }
6861         // ignore clicks on holdings
6862         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6863     }
6864
6865     if (clickType == Release && x == fromX && y == fromY) {
6866         DragPieceEnd(xPix, yPix); dragging = 0;
6867         if(clearFlag) {
6868             // a deferred attempt to click-click move an empty square on top of a piece
6869             boards[currentMove][y][x] = EmptySquare;
6870             ClearHighlights();
6871             DrawPosition(FALSE, boards[currentMove]);
6872             fromX = fromY = -1; clearFlag = 0;
6873             return;
6874         }
6875         if (appData.animateDragging) {
6876             /* Undo animation damage if any */
6877             DrawPosition(FALSE, NULL);
6878         }
6879         if (second) {
6880             /* Second up/down in same square; just abort move */
6881             second = 0;
6882             fromX = fromY = -1;
6883             gatingPiece = EmptySquare;
6884             ClearHighlights();
6885             gotPremove = 0;
6886             ClearPremoveHighlights();
6887         } else {
6888             /* First upclick in same square; start click-click mode */
6889             SetHighlights(x, y, -1, -1);
6890         }
6891         return;
6892     }
6893
6894     clearFlag = 0;
6895
6896     /* we now have a different from- and (possibly off-board) to-square */
6897     /* Completed move */
6898     toX = x;
6899     toY = y;
6900     saveAnimate = appData.animate;
6901     if (clickType == Press) {
6902         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6903             // must be Edit Position mode with empty-square selected
6904             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6905             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6906             return;
6907         }
6908         /* Finish clickclick move */
6909         if (appData.animate || appData.highlightLastMove) {
6910             SetHighlights(fromX, fromY, toX, toY);
6911         } else {
6912             ClearHighlights();
6913         }
6914     } else {
6915         /* Finish drag move */
6916         if (appData.highlightLastMove) {
6917             SetHighlights(fromX, fromY, toX, toY);
6918         } else {
6919             ClearHighlights();
6920         }
6921         DragPieceEnd(xPix, yPix); dragging = 0;
6922         /* Don't animate move and drag both */
6923         appData.animate = FALSE;
6924     }
6925
6926     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6927     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6928         ChessSquare piece = boards[currentMove][fromY][fromX];
6929         if(gameMode == EditPosition && piece != EmptySquare &&
6930            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6931             int n;
6932
6933             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6934                 n = PieceToNumber(piece - (int)BlackPawn);
6935                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6936                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6937                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6938             } else
6939             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6940                 n = PieceToNumber(piece);
6941                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6942                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6943                 boards[currentMove][n][BOARD_WIDTH-2]++;
6944             }
6945             boards[currentMove][fromY][fromX] = EmptySquare;
6946         }
6947         ClearHighlights();
6948         fromX = fromY = -1;
6949         DrawPosition(TRUE, boards[currentMove]);
6950         return;
6951     }
6952
6953     // off-board moves should not be highlighted
6954     if(x < 0 || y < 0) ClearHighlights();
6955
6956     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6957
6958     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6959         SetHighlights(fromX, fromY, toX, toY);
6960         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6961             // [HGM] super: promotion to captured piece selected from holdings
6962             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6963             promotionChoice = TRUE;
6964             // kludge follows to temporarily execute move on display, without promoting yet
6965             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6966             boards[currentMove][toY][toX] = p;
6967             DrawPosition(FALSE, boards[currentMove]);
6968             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6969             boards[currentMove][toY][toX] = q;
6970             DisplayMessage("Click in holdings to choose piece", "");
6971             return;
6972         }
6973         PromotionPopUp();
6974     } else {
6975         int oldMove = currentMove;
6976         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6977         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6978         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6979         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6980            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6981             DrawPosition(TRUE, boards[currentMove]);
6982         fromX = fromY = -1;
6983     }
6984     appData.animate = saveAnimate;
6985     if (appData.animate || appData.animateDragging) {
6986         /* Undo animation damage if needed */
6987         DrawPosition(FALSE, NULL);
6988     }
6989 }
6990
6991 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6992 {   // front-end-free part taken out of PieceMenuPopup
6993     int whichMenu; int xSqr, ySqr;
6994
6995     if(seekGraphUp) { // [HGM] seekgraph
6996         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6997         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6998         return -2;
6999     }
7000
7001     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7002          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7003         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7004         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7005         if(action == Press)   {
7006             originalFlip = flipView;
7007             flipView = !flipView; // temporarily flip board to see game from partners perspective
7008             DrawPosition(TRUE, partnerBoard);
7009             DisplayMessage(partnerStatus, "");
7010             partnerUp = TRUE;
7011         } else if(action == Release) {
7012             flipView = originalFlip;
7013             DrawPosition(TRUE, boards[currentMove]);
7014             partnerUp = FALSE;
7015         }
7016         return -2;
7017     }
7018
7019     xSqr = EventToSquare(x, BOARD_WIDTH);
7020     ySqr = EventToSquare(y, BOARD_HEIGHT);
7021     if (action == Release) {
7022         if(pieceSweep != EmptySquare) {
7023             EditPositionMenuEvent(pieceSweep, toX, toY);
7024             pieceSweep = EmptySquare;
7025         } else UnLoadPV(); // [HGM] pv
7026     }
7027     if (action != Press) return -2; // return code to be ignored
7028     switch (gameMode) {
7029       case IcsExamining:
7030         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7031       case EditPosition:
7032         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7033         if (xSqr < 0 || ySqr < 0) return -1;
7034         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7035         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7036         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7037         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7038         NextPiece(0);
7039         return -2;\r
7040       case IcsObserving:
7041         if(!appData.icsEngineAnalyze) return -1;
7042       case IcsPlayingWhite:
7043       case IcsPlayingBlack:
7044         if(!appData.zippyPlay) goto noZip;
7045       case AnalyzeMode:
7046       case AnalyzeFile:
7047       case MachinePlaysWhite:
7048       case MachinePlaysBlack:
7049       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7050         if (!appData.dropMenu) {
7051           LoadPV(x, y);
7052           return 2; // flag front-end to grab mouse events
7053         }
7054         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7055            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7056       case EditGame:
7057       noZip:
7058         if (xSqr < 0 || ySqr < 0) return -1;
7059         if (!appData.dropMenu || appData.testLegality &&
7060             gameInfo.variant != VariantBughouse &&
7061             gameInfo.variant != VariantCrazyhouse) return -1;
7062         whichMenu = 1; // drop menu
7063         break;
7064       default:
7065         return -1;
7066     }
7067
7068     if (((*fromX = xSqr) < 0) ||
7069         ((*fromY = ySqr) < 0)) {
7070         *fromX = *fromY = -1;
7071         return -1;
7072     }
7073     if (flipView)
7074       *fromX = BOARD_WIDTH - 1 - *fromX;
7075     else
7076       *fromY = BOARD_HEIGHT - 1 - *fromY;
7077
7078     return whichMenu;
7079 }
7080
7081 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7082 {
7083 //    char * hint = lastHint;
7084     FrontEndProgramStats stats;
7085
7086     stats.which = cps == &first ? 0 : 1;
7087     stats.depth = cpstats->depth;
7088     stats.nodes = cpstats->nodes;
7089     stats.score = cpstats->score;
7090     stats.time = cpstats->time;
7091     stats.pv = cpstats->movelist;
7092     stats.hint = lastHint;
7093     stats.an_move_index = 0;
7094     stats.an_move_count = 0;
7095
7096     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7097         stats.hint = cpstats->move_name;
7098         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7099         stats.an_move_count = cpstats->nr_moves;
7100     }
7101
7102     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
7103
7104     SetProgramStats( &stats );
7105 }
7106
7107 #define MAXPLAYERS 500
7108
7109 char *
7110 TourneyStandings(int display)
7111 {
7112     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7113     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7114     char result, *p, *names[MAXPLAYERS];
7115
7116     names[0] = p = strdup(appData.participants);
7117     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7118
7119     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7120
7121     while(result = appData.results[nr]) {
7122         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7123         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7124         wScore = bScore = 0;
7125         switch(result) {
7126           case '+': wScore = 2; break;
7127           case '-': bScore = 2; break;
7128           case '=': wScore = bScore = 1; break;
7129           case ' ':
7130           case '*': return strdup("busy"); // tourney not finished
7131         }
7132         score[w] += wScore;
7133         score[b] += bScore;
7134         games[w]++;
7135         games[b]++;
7136         nr++;
7137     }
7138     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7139     for(w=0; w<nPlayers; w++) {
7140         bScore = -1;
7141         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7142         ranking[w] = b; points[w] = bScore; score[b] = -2;
7143     }
7144     p = malloc(nPlayers*34+1);
7145     for(w=0; w<nPlayers && w<display; w++)
7146         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7147     free(names[0]);
7148     return p;
7149 }
7150
7151 void
7152 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7153 {       // count all piece types
7154         int p, f, r;
7155         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7156         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7157         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7158                 p = board[r][f];
7159                 pCnt[p]++;
7160                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7161                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7162                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7163                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7164                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7165                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7166         }
7167 }
7168
7169 int
7170 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7171 {
7172         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7173         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7174
7175         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7176         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7177         if(myPawns == 2 && nMine == 3) // KPP
7178             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7179         if(myPawns == 1 && nMine == 2) // KP
7180             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7181         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7182             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7183         if(myPawns) return FALSE;
7184         if(pCnt[WhiteRook+side])
7185             return pCnt[BlackRook-side] ||
7186                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7187                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7188                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7189         if(pCnt[WhiteCannon+side]) {
7190             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7191             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7192         }
7193         if(pCnt[WhiteKnight+side])
7194             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7195         return FALSE;
7196 }
7197
7198 int
7199 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7200 {
7201         VariantClass v = gameInfo.variant;
7202
7203         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7204         if(v == VariantShatranj) return TRUE; // always winnable through baring
7205         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7206         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7207
7208         if(v == VariantXiangqi) {
7209                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7210
7211                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7212                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7213                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7214                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7215                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7216                 if(stale) // we have at least one last-rank P plus perhaps C
7217                     return majors // KPKX
7218                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7219                 else // KCA*E*
7220                     return pCnt[WhiteFerz+side] // KCAK
7221                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7222                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7223                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7224
7225         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7226                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7227
7228                 if(nMine == 1) return FALSE; // bare King
7229                 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
7230                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7231                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7232                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7233                 if(pCnt[WhiteKnight+side])
7234                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7235                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7236                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7237                 if(nBishops)
7238                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7239                 if(pCnt[WhiteAlfil+side])
7240                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7241                 if(pCnt[WhiteWazir+side])
7242                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7243         }
7244
7245         return TRUE;
7246 }
7247
7248 int
7249 Adjudicate(ChessProgramState *cps)
7250 {       // [HGM] some adjudications useful with buggy engines
7251         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7252         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7253         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7254         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7255         int k, count = 0; static int bare = 1;
7256         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7257         Boolean canAdjudicate = !appData.icsActive;
7258
7259         // most tests only when we understand the game, i.e. legality-checking on
7260             if( appData.testLegality )
7261             {   /* [HGM] Some more adjudications for obstinate engines */
7262                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7263                 static int moveCount = 6;
7264                 ChessMove result;
7265                 char *reason = NULL;
7266
7267                 /* Count what is on board. */
7268                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7269
7270                 /* Some material-based adjudications that have to be made before stalemate test */
7271                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7272                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7273                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7274                      if(canAdjudicate && appData.checkMates) {
7275                          if(engineOpponent)
7276                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7277                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7278                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7279                          return 1;
7280                      }
7281                 }
7282
7283                 /* Bare King in Shatranj (loses) or Losers (wins) */
7284                 if( nrW == 1 || nrB == 1) {
7285                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7286                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7287                      if(canAdjudicate && appData.checkMates) {
7288                          if(engineOpponent)
7289                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7290                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7291                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7292                          return 1;
7293                      }
7294                   } else
7295                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7296                   {    /* bare King */
7297                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7298                         if(canAdjudicate && appData.checkMates) {
7299                             /* but only adjudicate if adjudication enabled */
7300                             if(engineOpponent)
7301                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7302                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7303                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7304                             return 1;
7305                         }
7306                   }
7307                 } else bare = 1;
7308
7309
7310             // don't wait for engine to announce game end if we can judge ourselves
7311             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7312               case MT_CHECK:
7313                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7314                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7315                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7316                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7317                             checkCnt++;
7318                         if(checkCnt >= 2) {
7319                             reason = "Xboard adjudication: 3rd check";
7320                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7321                             break;
7322                         }
7323                     }
7324                 }
7325               case MT_NONE:
7326               default:
7327                 break;
7328               case MT_STALEMATE:
7329               case MT_STAINMATE:
7330                 reason = "Xboard adjudication: Stalemate";
7331                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7332                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7333                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7334                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7335                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7336                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7337                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7338                                                                         EP_CHECKMATE : EP_WINS);
7339                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7340                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7341                 }
7342                 break;
7343               case MT_CHECKMATE:
7344                 reason = "Xboard adjudication: Checkmate";
7345                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7346                 break;
7347             }
7348
7349                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7350                     case EP_STALEMATE:
7351                         result = GameIsDrawn; break;
7352                     case EP_CHECKMATE:
7353                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7354                     case EP_WINS:
7355                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7356                     default:
7357                         result = EndOfFile;
7358                 }
7359                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7360                     if(engineOpponent)
7361                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7362                     GameEnds( result, reason, GE_XBOARD );
7363                     return 1;
7364                 }
7365
7366                 /* Next absolutely insufficient mating material. */
7367                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7368                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7369                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7370
7371                      /* always flag draws, for judging claims */
7372                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7373
7374                      if(canAdjudicate && appData.materialDraws) {
7375                          /* but only adjudicate them if adjudication enabled */
7376                          if(engineOpponent) {
7377                            SendToProgram("force\n", engineOpponent); // suppress reply
7378                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7379                          }
7380                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7381                          return 1;
7382                      }
7383                 }
7384
7385                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7386                 if(gameInfo.variant == VariantXiangqi ?
7387                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7388                  : nrW + nrB == 4 &&
7389                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7390                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7391                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7392                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7393                    ) ) {
7394                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7395                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7396                           if(engineOpponent) {
7397                             SendToProgram("force\n", engineOpponent); // suppress reply
7398                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7399                           }
7400                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7401                           return 1;
7402                      }
7403                 } else moveCount = 6;
7404             }
7405         if (appData.debugMode) { int i;
7406             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7407                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7408                     appData.drawRepeats);
7409             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7410               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7411
7412         }
7413
7414         // Repetition draws and 50-move rule can be applied independently of legality testing
7415
7416                 /* Check for rep-draws */
7417                 count = 0;
7418                 for(k = forwardMostMove-2;
7419                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7420                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7421                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7422                     k-=2)
7423                 {   int rights=0;
7424                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7425                         /* compare castling rights */
7426                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7427                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7428                                 rights++; /* King lost rights, while rook still had them */
7429                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7430                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7431                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7432                                    rights++; /* but at least one rook lost them */
7433                         }
7434                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7435                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7436                                 rights++;
7437                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7438                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7439                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7440                                    rights++;
7441                         }
7442                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7443                             && appData.drawRepeats > 1) {
7444                              /* adjudicate after user-specified nr of repeats */
7445                              int result = GameIsDrawn;
7446                              char *details = "XBoard adjudication: repetition draw";
7447                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7448                                 // [HGM] xiangqi: check for forbidden perpetuals
7449                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7450                                 for(m=forwardMostMove; m>k; m-=2) {
7451                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7452                                         ourPerpetual = 0; // the current mover did not always check
7453                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7454                                         hisPerpetual = 0; // the opponent did not always check
7455                                 }
7456                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7457                                                                         ourPerpetual, hisPerpetual);
7458                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7459                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7460                                     details = "Xboard adjudication: perpetual checking";
7461                                 } else
7462                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7463                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7464                                 } else
7465                                 // Now check for perpetual chases
7466                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7467                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7468                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7469                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7470                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7471                                         details = "Xboard adjudication: perpetual chasing";
7472                                     } else
7473                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7474                                         break; // Abort repetition-checking loop.
7475                                 }
7476                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7477                              }
7478                              if(engineOpponent) {
7479                                SendToProgram("force\n", engineOpponent); // suppress reply
7480                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7481                              }
7482                              GameEnds( result, details, GE_XBOARD );
7483                              return 1;
7484                         }
7485                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7486                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7487                     }
7488                 }
7489
7490                 /* Now we test for 50-move draws. Determine ply count */
7491                 count = forwardMostMove;
7492                 /* look for last irreversble move */
7493                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7494                     count--;
7495                 /* if we hit starting position, add initial plies */
7496                 if( count == backwardMostMove )
7497                     count -= initialRulePlies;
7498                 count = forwardMostMove - count;
7499                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7500                         // adjust reversible move counter for checks in Xiangqi
7501                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7502                         if(i < backwardMostMove) i = backwardMostMove;
7503                         while(i <= forwardMostMove) {
7504                                 lastCheck = inCheck; // check evasion does not count
7505                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7506                                 if(inCheck || lastCheck) count--; // check does not count
7507                                 i++;
7508                         }
7509                 }
7510                 if( count >= 100)
7511                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7512                          /* this is used to judge if draw claims are legal */
7513                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7514                          if(engineOpponent) {
7515                            SendToProgram("force\n", engineOpponent); // suppress reply
7516                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7517                          }
7518                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7519                          return 1;
7520                 }
7521
7522                 /* if draw offer is pending, treat it as a draw claim
7523                  * when draw condition present, to allow engines a way to
7524                  * claim draws before making their move to avoid a race
7525                  * condition occurring after their move
7526                  */
7527                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7528                          char *p = NULL;
7529                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7530                              p = "Draw claim: 50-move rule";
7531                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7532                              p = "Draw claim: 3-fold repetition";
7533                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7534                              p = "Draw claim: insufficient mating material";
7535                          if( p != NULL && canAdjudicate) {
7536                              if(engineOpponent) {
7537                                SendToProgram("force\n", engineOpponent); // suppress reply
7538                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7539                              }
7540                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7541                              return 1;
7542                          }
7543                 }
7544
7545                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7546                     if(engineOpponent) {
7547                       SendToProgram("force\n", engineOpponent); // suppress reply
7548                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7549                     }
7550                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7551                     return 1;
7552                 }
7553         return 0;
7554 }
7555
7556 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7557 {   // [HGM] book: this routine intercepts moves to simulate book replies
7558     char *bookHit = NULL;
7559
7560     //first determine if the incoming move brings opponent into his book
7561     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7562         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7563     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7564     if(bookHit != NULL && !cps->bookSuspend) {
7565         // make sure opponent is not going to reply after receiving move to book position
7566         SendToProgram("force\n", cps);
7567         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7568     }
7569     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7570     // now arrange restart after book miss
7571     if(bookHit) {
7572         // after a book hit we never send 'go', and the code after the call to this routine
7573         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7574         char buf[MSG_SIZ];
7575         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7576         SendToProgram(buf, cps);
7577         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7578     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7579         SendToProgram("go\n", cps);
7580         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7581     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7582         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7583             SendToProgram("go\n", cps);
7584         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7585     }
7586     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7587 }
7588
7589 char *savedMessage;
7590 ChessProgramState *savedState;
7591 void DeferredBookMove(void)
7592 {
7593         if(savedState->lastPing != savedState->lastPong)
7594                     ScheduleDelayedEvent(DeferredBookMove, 10);
7595         else
7596         HandleMachineMove(savedMessage, savedState);
7597 }
7598
7599 void
7600 HandleMachineMove(message, cps)
7601      char *message;
7602      ChessProgramState *cps;
7603 {
7604     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7605     char realname[MSG_SIZ];
7606     int fromX, fromY, toX, toY;
7607     ChessMove moveType;
7608     char promoChar;
7609     char *p;
7610     int machineWhite;
7611     char *bookHit;
7612
7613     cps->userError = 0;
7614
7615 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7616     /*
7617      * Kludge to ignore BEL characters
7618      */
7619     while (*message == '\007') message++;
7620
7621     /*
7622      * [HGM] engine debug message: ignore lines starting with '#' character
7623      */
7624     if(cps->debug && *message == '#') return;
7625
7626     /*
7627      * Look for book output
7628      */
7629     if (cps == &first && bookRequested) {
7630         if (message[0] == '\t' || message[0] == ' ') {
7631             /* Part of the book output is here; append it */
7632             strcat(bookOutput, message);
7633             strcat(bookOutput, "  \n");
7634             return;
7635         } else if (bookOutput[0] != NULLCHAR) {
7636             /* All of book output has arrived; display it */
7637             char *p = bookOutput;
7638             while (*p != NULLCHAR) {
7639                 if (*p == '\t') *p = ' ';
7640                 p++;
7641             }
7642             DisplayInformation(bookOutput);
7643             bookRequested = FALSE;
7644             /* Fall through to parse the current output */
7645         }
7646     }
7647
7648     /*
7649      * Look for machine move.
7650      */
7651     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7652         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7653     {
7654         /* This method is only useful on engines that support ping */
7655         if (cps->lastPing != cps->lastPong) {
7656           if (gameMode == BeginningOfGame) {
7657             /* Extra move from before last new; ignore */
7658             if (appData.debugMode) {
7659                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7660             }
7661           } else {
7662             if (appData.debugMode) {
7663                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7664                         cps->which, gameMode);
7665             }
7666
7667             SendToProgram("undo\n", cps);
7668           }
7669           return;
7670         }
7671
7672         switch (gameMode) {
7673           case BeginningOfGame:
7674             /* Extra move from before last reset; ignore */
7675             if (appData.debugMode) {
7676                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7677             }
7678             return;
7679
7680           case EndOfGame:
7681           case IcsIdle:
7682           default:
7683             /* Extra move after we tried to stop.  The mode test is
7684                not a reliable way of detecting this problem, but it's
7685                the best we can do on engines that don't support ping.
7686             */
7687             if (appData.debugMode) {
7688                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7689                         cps->which, gameMode);
7690             }
7691             SendToProgram("undo\n", cps);
7692             return;
7693
7694           case MachinePlaysWhite:
7695           case IcsPlayingWhite:
7696             machineWhite = TRUE;
7697             break;
7698
7699           case MachinePlaysBlack:
7700           case IcsPlayingBlack:
7701             machineWhite = FALSE;
7702             break;
7703
7704           case TwoMachinesPlay:
7705             machineWhite = (cps->twoMachinesColor[0] == 'w');
7706             break;
7707         }
7708         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7709             if (appData.debugMode) {
7710                 fprintf(debugFP,
7711                         "Ignoring move out of turn by %s, gameMode %d"
7712                         ", forwardMost %d\n",
7713                         cps->which, gameMode, forwardMostMove);
7714             }
7715             return;
7716         }
7717
7718     if (appData.debugMode) { int f = forwardMostMove;
7719         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7720                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7721                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7722     }
7723         if(cps->alphaRank) AlphaRank(machineMove, 4);
7724         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7725                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7726             /* Machine move could not be parsed; ignore it. */
7727           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7728                     machineMove, _(cps->which));
7729             DisplayError(buf1, 0);
7730             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7731                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7732             if (gameMode == TwoMachinesPlay) {
7733               GameEnds(machineWhite ? BlackWins : WhiteWins,
7734                        buf1, GE_XBOARD);
7735             }
7736             return;
7737         }
7738
7739         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7740         /* So we have to redo legality test with true e.p. status here,  */
7741         /* to make sure an illegal e.p. capture does not slip through,   */
7742         /* to cause a forfeit on a justified illegal-move complaint      */
7743         /* of the opponent.                                              */
7744         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7745            ChessMove moveType;
7746            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7747                              fromY, fromX, toY, toX, promoChar);
7748             if (appData.debugMode) {
7749                 int i;
7750                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7751                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7752                 fprintf(debugFP, "castling rights\n");
7753             }
7754             if(moveType == IllegalMove) {
7755               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7756                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7757                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7758                            buf1, GE_XBOARD);
7759                 return;
7760            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7761            /* [HGM] Kludge to handle engines that send FRC-style castling
7762               when they shouldn't (like TSCP-Gothic) */
7763            switch(moveType) {
7764              case WhiteASideCastleFR:
7765              case BlackASideCastleFR:
7766                toX+=2;
7767                currentMoveString[2]++;
7768                break;
7769              case WhiteHSideCastleFR:
7770              case BlackHSideCastleFR:
7771                toX--;
7772                currentMoveString[2]--;
7773                break;
7774              default: ; // nothing to do, but suppresses warning of pedantic compilers
7775            }
7776         }
7777         hintRequested = FALSE;
7778         lastHint[0] = NULLCHAR;
7779         bookRequested = FALSE;
7780         /* Program may be pondering now */
7781         cps->maybeThinking = TRUE;
7782         if (cps->sendTime == 2) cps->sendTime = 1;
7783         if (cps->offeredDraw) cps->offeredDraw--;
7784
7785         /* [AS] Save move info*/
7786         pvInfoList[ forwardMostMove ].score = programStats.score;
7787         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7788         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7789
7790         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7791
7792         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7793         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7794             int count = 0;
7795
7796             while( count < adjudicateLossPlies ) {
7797                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7798
7799                 if( count & 1 ) {
7800                     score = -score; /* Flip score for winning side */
7801                 }
7802
7803                 if( score > adjudicateLossThreshold ) {
7804                     break;
7805                 }
7806
7807                 count++;
7808             }
7809
7810             if( count >= adjudicateLossPlies ) {
7811                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7812
7813                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7814                     "Xboard adjudication",
7815                     GE_XBOARD );
7816
7817                 return;
7818             }
7819         }
7820
7821         if(Adjudicate(cps)) {
7822             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7823             return; // [HGM] adjudicate: for all automatic game ends
7824         }
7825
7826 #if ZIPPY
7827         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7828             first.initDone) {
7829           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7830                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7831                 SendToICS("draw ");
7832                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7833           }
7834           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7835           ics_user_moved = 1;
7836           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7837                 char buf[3*MSG_SIZ];
7838
7839                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7840                         programStats.score / 100.,
7841                         programStats.depth,
7842                         programStats.time / 100.,
7843                         (unsigned int)programStats.nodes,
7844                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7845                         programStats.movelist);
7846                 SendToICS(buf);
7847 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7848           }
7849         }
7850 #endif
7851
7852         /* [AS] Clear stats for next move */
7853         ClearProgramStats();
7854         thinkOutput[0] = NULLCHAR;
7855         hiddenThinkOutputState = 0;
7856
7857         bookHit = NULL;
7858         if (gameMode == TwoMachinesPlay) {
7859             /* [HGM] relaying draw offers moved to after reception of move */
7860             /* and interpreting offer as claim if it brings draw condition */
7861             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7862                 SendToProgram("draw\n", cps->other);
7863             }
7864             if (cps->other->sendTime) {
7865                 SendTimeRemaining(cps->other,
7866                                   cps->other->twoMachinesColor[0] == 'w');
7867             }
7868             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7869             if (firstMove && !bookHit) {
7870                 firstMove = FALSE;
7871                 if (cps->other->useColors) {
7872                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7873                 }
7874                 SendToProgram("go\n", cps->other);
7875             }
7876             cps->other->maybeThinking = TRUE;
7877         }
7878
7879         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7880
7881         if (!pausing && appData.ringBellAfterMoves) {
7882             RingBell();
7883         }
7884
7885         /*
7886          * Reenable menu items that were disabled while
7887          * machine was thinking
7888          */
7889         if (gameMode != TwoMachinesPlay)
7890             SetUserThinkingEnables();
7891
7892         // [HGM] book: after book hit opponent has received move and is now in force mode
7893         // force the book reply into it, and then fake that it outputted this move by jumping
7894         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7895         if(bookHit) {
7896                 static char bookMove[MSG_SIZ]; // a bit generous?
7897
7898                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7899                 strcat(bookMove, bookHit);
7900                 message = bookMove;
7901                 cps = cps->other;
7902                 programStats.nodes = programStats.depth = programStats.time =
7903                 programStats.score = programStats.got_only_move = 0;
7904                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7905
7906                 if(cps->lastPing != cps->lastPong) {
7907                     savedMessage = message; // args for deferred call
7908                     savedState = cps;
7909                     ScheduleDelayedEvent(DeferredBookMove, 10);
7910                     return;
7911                 }
7912                 goto FakeBookMove;
7913         }
7914
7915         return;
7916     }
7917
7918     /* Set special modes for chess engines.  Later something general
7919      *  could be added here; for now there is just one kludge feature,
7920      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7921      *  when "xboard" is given as an interactive command.
7922      */
7923     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7924         cps->useSigint = FALSE;
7925         cps->useSigterm = FALSE;
7926     }
7927     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7928       ParseFeatures(message+8, cps);
7929       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7930     }
7931
7932     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7933       int dummy, s=6; char buf[MSG_SIZ];
7934       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7935       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7936       ParseFEN(boards[0], &dummy, message+s);
7937       DrawPosition(TRUE, boards[0]);
7938       startedFromSetupPosition = TRUE;
7939       return;
7940     }
7941     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7942      * want this, I was asked to put it in, and obliged.
7943      */
7944     if (!strncmp(message, "setboard ", 9)) {
7945         Board initial_position;
7946
7947         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7948
7949         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7950             DisplayError(_("Bad FEN received from engine"), 0);
7951             return ;
7952         } else {
7953            Reset(TRUE, FALSE);
7954            CopyBoard(boards[0], initial_position);
7955            initialRulePlies = FENrulePlies;
7956            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7957            else gameMode = MachinePlaysBlack;
7958            DrawPosition(FALSE, boards[currentMove]);
7959         }
7960         return;
7961     }
7962
7963     /*
7964      * Look for communication commands
7965      */
7966     if (!strncmp(message, "telluser ", 9)) {
7967         if(message[9] == '\\' && message[10] == '\\')
7968             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7969         DisplayNote(message + 9);
7970         return;
7971     }
7972     if (!strncmp(message, "tellusererror ", 14)) {
7973         cps->userError = 1;
7974         if(message[14] == '\\' && message[15] == '\\')
7975             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7976         DisplayError(message + 14, 0);
7977         return;
7978     }
7979     if (!strncmp(message, "tellopponent ", 13)) {
7980       if (appData.icsActive) {
7981         if (loggedOn) {
7982           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7983           SendToICS(buf1);
7984         }
7985       } else {
7986         DisplayNote(message + 13);
7987       }
7988       return;
7989     }
7990     if (!strncmp(message, "tellothers ", 11)) {
7991       if (appData.icsActive) {
7992         if (loggedOn) {
7993           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7994           SendToICS(buf1);
7995         }
7996       }
7997       return;
7998     }
7999     if (!strncmp(message, "tellall ", 8)) {
8000       if (appData.icsActive) {
8001         if (loggedOn) {
8002           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8003           SendToICS(buf1);
8004         }
8005       } else {
8006         DisplayNote(message + 8);
8007       }
8008       return;
8009     }
8010     if (strncmp(message, "warning", 7) == 0) {
8011         /* Undocumented feature, use tellusererror in new code */
8012         DisplayError(message, 0);
8013         return;
8014     }
8015     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8016         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8017         strcat(realname, " query");
8018         AskQuestion(realname, buf2, buf1, cps->pr);
8019         return;
8020     }
8021     /* Commands from the engine directly to ICS.  We don't allow these to be
8022      *  sent until we are logged on. Crafty kibitzes have been known to
8023      *  interfere with the login process.
8024      */
8025     if (loggedOn) {
8026         if (!strncmp(message, "tellics ", 8)) {
8027             SendToICS(message + 8);
8028             SendToICS("\n");
8029             return;
8030         }
8031         if (!strncmp(message, "tellicsnoalias ", 15)) {
8032             SendToICS(ics_prefix);
8033             SendToICS(message + 15);
8034             SendToICS("\n");
8035             return;
8036         }
8037         /* The following are for backward compatibility only */
8038         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8039             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8040             SendToICS(ics_prefix);
8041             SendToICS(message);
8042             SendToICS("\n");
8043             return;
8044         }
8045     }
8046     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8047         return;
8048     }
8049     /*
8050      * If the move is illegal, cancel it and redraw the board.
8051      * Also deal with other error cases.  Matching is rather loose
8052      * here to accommodate engines written before the spec.
8053      */
8054     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8055         strncmp(message, "Error", 5) == 0) {
8056         if (StrStr(message, "name") ||
8057             StrStr(message, "rating") || StrStr(message, "?") ||
8058             StrStr(message, "result") || StrStr(message, "board") ||
8059             StrStr(message, "bk") || StrStr(message, "computer") ||
8060             StrStr(message, "variant") || StrStr(message, "hint") ||
8061             StrStr(message, "random") || StrStr(message, "depth") ||
8062             StrStr(message, "accepted")) {
8063             return;
8064         }
8065         if (StrStr(message, "protover")) {
8066           /* Program is responding to input, so it's apparently done
8067              initializing, and this error message indicates it is
8068              protocol version 1.  So we don't need to wait any longer
8069              for it to initialize and send feature commands. */
8070           FeatureDone(cps, 1);
8071           cps->protocolVersion = 1;
8072           return;
8073         }
8074         cps->maybeThinking = FALSE;
8075
8076         if (StrStr(message, "draw")) {
8077             /* Program doesn't have "draw" command */
8078             cps->sendDrawOffers = 0;
8079             return;
8080         }
8081         if (cps->sendTime != 1 &&
8082             (StrStr(message, "time") || StrStr(message, "otim"))) {
8083           /* Program apparently doesn't have "time" or "otim" command */
8084           cps->sendTime = 0;
8085           return;
8086         }
8087         if (StrStr(message, "analyze")) {
8088             cps->analysisSupport = FALSE;
8089             cps->analyzing = FALSE;
8090             Reset(FALSE, TRUE);
8091             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8092             DisplayError(buf2, 0);
8093             return;
8094         }
8095         if (StrStr(message, "(no matching move)st")) {
8096           /* Special kludge for GNU Chess 4 only */
8097           cps->stKludge = TRUE;
8098           SendTimeControl(cps, movesPerSession, timeControl,
8099                           timeIncrement, appData.searchDepth,
8100                           searchTime);
8101           return;
8102         }
8103         if (StrStr(message, "(no matching move)sd")) {
8104           /* Special kludge for GNU Chess 4 only */
8105           cps->sdKludge = TRUE;
8106           SendTimeControl(cps, movesPerSession, timeControl,
8107                           timeIncrement, appData.searchDepth,
8108                           searchTime);
8109           return;
8110         }
8111         if (!StrStr(message, "llegal")) {
8112             return;
8113         }
8114         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8115             gameMode == IcsIdle) return;
8116         if (forwardMostMove <= backwardMostMove) return;
8117         if (pausing) PauseEvent();
8118       if(appData.forceIllegal) {
8119             // [HGM] illegal: machine refused move; force position after move into it
8120           SendToProgram("force\n", cps);
8121           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8122                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8123                 // when black is to move, while there might be nothing on a2 or black
8124                 // might already have the move. So send the board as if white has the move.
8125                 // But first we must change the stm of the engine, as it refused the last move
8126                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8127                 if(WhiteOnMove(forwardMostMove)) {
8128                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8129                     SendBoard(cps, forwardMostMove); // kludgeless board
8130                 } else {
8131                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8132                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8133                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8134                 }
8135           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8136             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8137                  gameMode == TwoMachinesPlay)
8138               SendToProgram("go\n", cps);
8139             return;
8140       } else
8141         if (gameMode == PlayFromGameFile) {
8142             /* Stop reading this game file */
8143             gameMode = EditGame;
8144             ModeHighlight();
8145         }
8146         /* [HGM] illegal-move claim should forfeit game when Xboard */
8147         /* only passes fully legal moves                            */
8148         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8149             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8150                                 "False illegal-move claim", GE_XBOARD );
8151             return; // do not take back move we tested as valid
8152         }
8153         currentMove = forwardMostMove-1;
8154         DisplayMove(currentMove-1); /* before DisplayMoveError */
8155         SwitchClocks(forwardMostMove-1); // [HGM] race
8156         DisplayBothClocks();
8157         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8158                 parseList[currentMove], _(cps->which));
8159         DisplayMoveError(buf1);
8160         DrawPosition(FALSE, boards[currentMove]);
8161         return;
8162     }
8163     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8164         /* Program has a broken "time" command that
8165            outputs a string not ending in newline.
8166            Don't use it. */
8167         cps->sendTime = 0;
8168     }
8169
8170     /*
8171      * If chess program startup fails, exit with an error message.
8172      * Attempts to recover here are futile.
8173      */
8174     if ((StrStr(message, "unknown host") != NULL)
8175         || (StrStr(message, "No remote directory") != NULL)
8176         || (StrStr(message, "not found") != NULL)
8177         || (StrStr(message, "No such file") != NULL)
8178         || (StrStr(message, "can't alloc") != NULL)
8179         || (StrStr(message, "Permission denied") != NULL)) {
8180
8181         cps->maybeThinking = FALSE;
8182         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8183                 _(cps->which), cps->program, cps->host, message);
8184         RemoveInputSource(cps->isr);
8185         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8186             if(cps == &first) appData.noChessProgram = TRUE;
8187             DisplayError(buf1, 0);
8188         }
8189         return;
8190     }
8191
8192     /*
8193      * Look for hint output
8194      */
8195     if (sscanf(message, "Hint: %s", buf1) == 1) {
8196         if (cps == &first && hintRequested) {
8197             hintRequested = FALSE;
8198             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8199                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8200                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8201                                     PosFlags(forwardMostMove),
8202                                     fromY, fromX, toY, toX, promoChar, buf1);
8203                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8204                 DisplayInformation(buf2);
8205             } else {
8206                 /* Hint move could not be parsed!? */
8207               snprintf(buf2, sizeof(buf2),
8208                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8209                         buf1, _(cps->which));
8210                 DisplayError(buf2, 0);
8211             }
8212         } else {
8213           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8214         }
8215         return;
8216     }
8217
8218     /*
8219      * Ignore other messages if game is not in progress
8220      */
8221     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8222         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8223
8224     /*
8225      * look for win, lose, draw, or draw offer
8226      */
8227     if (strncmp(message, "1-0", 3) == 0) {
8228         char *p, *q, *r = "";
8229         p = strchr(message, '{');
8230         if (p) {
8231             q = strchr(p, '}');
8232             if (q) {
8233                 *q = NULLCHAR;
8234                 r = p + 1;
8235             }
8236         }
8237         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8238         return;
8239     } else if (strncmp(message, "0-1", 3) == 0) {
8240         char *p, *q, *r = "";
8241         p = strchr(message, '{');
8242         if (p) {
8243             q = strchr(p, '}');
8244             if (q) {
8245                 *q = NULLCHAR;
8246                 r = p + 1;
8247             }
8248         }
8249         /* Kludge for Arasan 4.1 bug */
8250         if (strcmp(r, "Black resigns") == 0) {
8251             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8252             return;
8253         }
8254         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8255         return;
8256     } else if (strncmp(message, "1/2", 3) == 0) {
8257         char *p, *q, *r = "";
8258         p = strchr(message, '{');
8259         if (p) {
8260             q = strchr(p, '}');
8261             if (q) {
8262                 *q = NULLCHAR;
8263                 r = p + 1;
8264             }
8265         }
8266
8267         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8268         return;
8269
8270     } else if (strncmp(message, "White resign", 12) == 0) {
8271         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8272         return;
8273     } else if (strncmp(message, "Black resign", 12) == 0) {
8274         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8275         return;
8276     } else if (strncmp(message, "White matches", 13) == 0 ||
8277                strncmp(message, "Black matches", 13) == 0   ) {
8278         /* [HGM] ignore GNUShogi noises */
8279         return;
8280     } else if (strncmp(message, "White", 5) == 0 &&
8281                message[5] != '(' &&
8282                StrStr(message, "Black") == NULL) {
8283         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8284         return;
8285     } else if (strncmp(message, "Black", 5) == 0 &&
8286                message[5] != '(') {
8287         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8288         return;
8289     } else if (strcmp(message, "resign") == 0 ||
8290                strcmp(message, "computer resigns") == 0) {
8291         switch (gameMode) {
8292           case MachinePlaysBlack:
8293           case IcsPlayingBlack:
8294             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8295             break;
8296           case MachinePlaysWhite:
8297           case IcsPlayingWhite:
8298             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8299             break;
8300           case TwoMachinesPlay:
8301             if (cps->twoMachinesColor[0] == 'w')
8302               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8303             else
8304               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8305             break;
8306           default:
8307             /* can't happen */
8308             break;
8309         }
8310         return;
8311     } else if (strncmp(message, "opponent mates", 14) == 0) {
8312         switch (gameMode) {
8313           case MachinePlaysBlack:
8314           case IcsPlayingBlack:
8315             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8316             break;
8317           case MachinePlaysWhite:
8318           case IcsPlayingWhite:
8319             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8320             break;
8321           case TwoMachinesPlay:
8322             if (cps->twoMachinesColor[0] == 'w')
8323               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8324             else
8325               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8326             break;
8327           default:
8328             /* can't happen */
8329             break;
8330         }
8331         return;
8332     } else if (strncmp(message, "computer mates", 14) == 0) {
8333         switch (gameMode) {
8334           case MachinePlaysBlack:
8335           case IcsPlayingBlack:
8336             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8337             break;
8338           case MachinePlaysWhite:
8339           case IcsPlayingWhite:
8340             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8341             break;
8342           case TwoMachinesPlay:
8343             if (cps->twoMachinesColor[0] == 'w')
8344               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8345             else
8346               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8347             break;
8348           default:
8349             /* can't happen */
8350             break;
8351         }
8352         return;
8353     } else if (strncmp(message, "checkmate", 9) == 0) {
8354         if (WhiteOnMove(forwardMostMove)) {
8355             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8356         } else {
8357             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8358         }
8359         return;
8360     } else if (strstr(message, "Draw") != NULL ||
8361                strstr(message, "game is a draw") != NULL) {
8362         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8363         return;
8364     } else if (strstr(message, "offer") != NULL &&
8365                strstr(message, "draw") != NULL) {
8366 #if ZIPPY
8367         if (appData.zippyPlay && first.initDone) {
8368             /* Relay offer to ICS */
8369             SendToICS(ics_prefix);
8370             SendToICS("draw\n");
8371         }
8372 #endif
8373         cps->offeredDraw = 2; /* valid until this engine moves twice */
8374         if (gameMode == TwoMachinesPlay) {
8375             if (cps->other->offeredDraw) {
8376                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8377             /* [HGM] in two-machine mode we delay relaying draw offer      */
8378             /* until after we also have move, to see if it is really claim */
8379             }
8380         } else if (gameMode == MachinePlaysWhite ||
8381                    gameMode == MachinePlaysBlack) {
8382           if (userOfferedDraw) {
8383             DisplayInformation(_("Machine accepts your draw offer"));
8384             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8385           } else {
8386             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8387           }
8388         }
8389     }
8390
8391
8392     /*
8393      * Look for thinking output
8394      */
8395     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8396           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8397                                 ) {
8398         int plylev, mvleft, mvtot, curscore, time;
8399         char mvname[MOVE_LEN];
8400         u64 nodes; // [DM]
8401         char plyext;
8402         int ignore = FALSE;
8403         int prefixHint = FALSE;
8404         mvname[0] = NULLCHAR;
8405
8406         switch (gameMode) {
8407           case MachinePlaysBlack:
8408           case IcsPlayingBlack:
8409             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8410             break;
8411           case MachinePlaysWhite:
8412           case IcsPlayingWhite:
8413             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8414             break;
8415           case AnalyzeMode:
8416           case AnalyzeFile:
8417             break;
8418           case IcsObserving: /* [DM] icsEngineAnalyze */
8419             if (!appData.icsEngineAnalyze) ignore = TRUE;
8420             break;
8421           case TwoMachinesPlay:
8422             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8423                 ignore = TRUE;
8424             }
8425             break;
8426           default:
8427             ignore = TRUE;
8428             break;
8429         }
8430
8431         if (!ignore) {
8432             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8433             buf1[0] = NULLCHAR;
8434             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8435                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8436
8437                 if (plyext != ' ' && plyext != '\t') {
8438                     time *= 100;
8439                 }
8440
8441                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8442                 if( cps->scoreIsAbsolute &&
8443                     ( gameMode == MachinePlaysBlack ||
8444                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8445                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8446                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8447                      !WhiteOnMove(currentMove)
8448                     ) )
8449                 {
8450                     curscore = -curscore;
8451                 }
8452
8453
8454                 tempStats.depth = plylev;
8455                 tempStats.nodes = nodes;
8456                 tempStats.time = time;
8457                 tempStats.score = curscore;
8458                 tempStats.got_only_move = 0;
8459
8460                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8461                         int ticklen;
8462
8463                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8464                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8465                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8466                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8467                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8468                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8469                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8470                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8471                 }
8472
8473                 /* Buffer overflow protection */
8474                 if (buf1[0] != NULLCHAR) {
8475                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8476                         && appData.debugMode) {
8477                         fprintf(debugFP,
8478                                 "PV is too long; using the first %u bytes.\n",
8479                                 (unsigned) sizeof(tempStats.movelist) - 1);
8480                     }
8481
8482                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8483                 } else {
8484                     sprintf(tempStats.movelist, " no PV\n");
8485                 }
8486
8487                 if (tempStats.seen_stat) {
8488                     tempStats.ok_to_send = 1;
8489                 }
8490
8491                 if (strchr(tempStats.movelist, '(') != NULL) {
8492                     tempStats.line_is_book = 1;
8493                     tempStats.nr_moves = 0;
8494                     tempStats.moves_left = 0;
8495                 } else {
8496                     tempStats.line_is_book = 0;
8497                 }
8498
8499                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8500                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8501
8502                 SendProgramStatsToFrontend( cps, &tempStats );
8503
8504                 /*
8505                     [AS] Protect the thinkOutput buffer from overflow... this
8506                     is only useful if buf1 hasn't overflowed first!
8507                 */
8508                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8509                          plylev,
8510                          (gameMode == TwoMachinesPlay ?
8511                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8512                          ((double) curscore) / 100.0,
8513                          prefixHint ? lastHint : "",
8514                          prefixHint ? " " : "" );
8515
8516                 if( buf1[0] != NULLCHAR ) {
8517                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8518
8519                     if( strlen(buf1) > max_len ) {
8520                         if( appData.debugMode) {
8521                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8522                         }
8523                         buf1[max_len+1] = '\0';
8524                     }
8525
8526                     strcat( thinkOutput, buf1 );
8527                 }
8528
8529                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8530                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8531                     DisplayMove(currentMove - 1);
8532                 }
8533                 return;
8534
8535             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8536                 /* crafty (9.25+) says "(only move) <move>"
8537                  * if there is only 1 legal move
8538                  */
8539                 sscanf(p, "(only move) %s", buf1);
8540                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8541                 sprintf(programStats.movelist, "%s (only move)", buf1);
8542                 programStats.depth = 1;
8543                 programStats.nr_moves = 1;
8544                 programStats.moves_left = 1;
8545                 programStats.nodes = 1;
8546                 programStats.time = 1;
8547                 programStats.got_only_move = 1;
8548
8549                 /* Not really, but we also use this member to
8550                    mean "line isn't going to change" (Crafty
8551                    isn't searching, so stats won't change) */
8552                 programStats.line_is_book = 1;
8553
8554                 SendProgramStatsToFrontend( cps, &programStats );
8555
8556                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8557                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8558                     DisplayMove(currentMove - 1);
8559                 }
8560                 return;
8561             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8562                               &time, &nodes, &plylev, &mvleft,
8563                               &mvtot, mvname) >= 5) {
8564                 /* The stat01: line is from Crafty (9.29+) in response
8565                    to the "." command */
8566                 programStats.seen_stat = 1;
8567                 cps->maybeThinking = TRUE;
8568
8569                 if (programStats.got_only_move || !appData.periodicUpdates)
8570                   return;
8571
8572                 programStats.depth = plylev;
8573                 programStats.time = time;
8574                 programStats.nodes = nodes;
8575                 programStats.moves_left = mvleft;
8576                 programStats.nr_moves = mvtot;
8577                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8578                 programStats.ok_to_send = 1;
8579                 programStats.movelist[0] = '\0';
8580
8581                 SendProgramStatsToFrontend( cps, &programStats );
8582
8583                 return;
8584
8585             } else if (strncmp(message,"++",2) == 0) {
8586                 /* Crafty 9.29+ outputs this */
8587                 programStats.got_fail = 2;
8588                 return;
8589
8590             } else if (strncmp(message,"--",2) == 0) {
8591                 /* Crafty 9.29+ outputs this */
8592                 programStats.got_fail = 1;
8593                 return;
8594
8595             } else if (thinkOutput[0] != NULLCHAR &&
8596                        strncmp(message, "    ", 4) == 0) {
8597                 unsigned message_len;
8598
8599                 p = message;
8600                 while (*p && *p == ' ') p++;
8601
8602                 message_len = strlen( p );
8603
8604                 /* [AS] Avoid buffer overflow */
8605                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8606                     strcat(thinkOutput, " ");
8607                     strcat(thinkOutput, p);
8608                 }
8609
8610                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8611                     strcat(programStats.movelist, " ");
8612                     strcat(programStats.movelist, p);
8613                 }
8614
8615                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8616                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8617                     DisplayMove(currentMove - 1);
8618                 }
8619                 return;
8620             }
8621         }
8622         else {
8623             buf1[0] = NULLCHAR;
8624
8625             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8626                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8627             {
8628                 ChessProgramStats cpstats;
8629
8630                 if (plyext != ' ' && plyext != '\t') {
8631                     time *= 100;
8632                 }
8633
8634                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8635                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8636                     curscore = -curscore;
8637                 }
8638
8639                 cpstats.depth = plylev;
8640                 cpstats.nodes = nodes;
8641                 cpstats.time = time;
8642                 cpstats.score = curscore;
8643                 cpstats.got_only_move = 0;
8644                 cpstats.movelist[0] = '\0';
8645
8646                 if (buf1[0] != NULLCHAR) {
8647                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8648                 }
8649
8650                 cpstats.ok_to_send = 0;
8651                 cpstats.line_is_book = 0;
8652                 cpstats.nr_moves = 0;
8653                 cpstats.moves_left = 0;
8654
8655                 SendProgramStatsToFrontend( cps, &cpstats );
8656             }
8657         }
8658     }
8659 }
8660
8661
8662 /* Parse a game score from the character string "game", and
8663    record it as the history of the current game.  The game
8664    score is NOT assumed to start from the standard position.
8665    The display is not updated in any way.
8666    */
8667 void
8668 ParseGameHistory(game)
8669      char *game;
8670 {
8671     ChessMove moveType;
8672     int fromX, fromY, toX, toY, boardIndex;
8673     char promoChar;
8674     char *p, *q;
8675     char buf[MSG_SIZ];
8676
8677     if (appData.debugMode)
8678       fprintf(debugFP, "Parsing game history: %s\n", game);
8679
8680     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8681     gameInfo.site = StrSave(appData.icsHost);
8682     gameInfo.date = PGNDate();
8683     gameInfo.round = StrSave("-");
8684
8685     /* Parse out names of players */
8686     while (*game == ' ') game++;
8687     p = buf;
8688     while (*game != ' ') *p++ = *game++;
8689     *p = NULLCHAR;
8690     gameInfo.white = StrSave(buf);
8691     while (*game == ' ') game++;
8692     p = buf;
8693     while (*game != ' ' && *game != '\n') *p++ = *game++;
8694     *p = NULLCHAR;
8695     gameInfo.black = StrSave(buf);
8696
8697     /* Parse moves */
8698     boardIndex = blackPlaysFirst ? 1 : 0;
8699     yynewstr(game);
8700     for (;;) {
8701         yyboardindex = boardIndex;
8702         moveType = (ChessMove) Myylex();
8703         switch (moveType) {
8704           case IllegalMove:             /* maybe suicide chess, etc. */
8705   if (appData.debugMode) {
8706     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8707     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8708     setbuf(debugFP, NULL);
8709   }
8710           case WhitePromotion:
8711           case BlackPromotion:
8712           case WhiteNonPromotion:
8713           case BlackNonPromotion:
8714           case NormalMove:
8715           case WhiteCapturesEnPassant:
8716           case BlackCapturesEnPassant:
8717           case WhiteKingSideCastle:
8718           case WhiteQueenSideCastle:
8719           case BlackKingSideCastle:
8720           case BlackQueenSideCastle:
8721           case WhiteKingSideCastleWild:
8722           case WhiteQueenSideCastleWild:
8723           case BlackKingSideCastleWild:
8724           case BlackQueenSideCastleWild:
8725           /* PUSH Fabien */
8726           case WhiteHSideCastleFR:
8727           case WhiteASideCastleFR:
8728           case BlackHSideCastleFR:
8729           case BlackASideCastleFR:
8730           /* POP Fabien */
8731             fromX = currentMoveString[0] - AAA;
8732             fromY = currentMoveString[1] - ONE;
8733             toX = currentMoveString[2] - AAA;
8734             toY = currentMoveString[3] - ONE;
8735             promoChar = currentMoveString[4];
8736             break;
8737           case WhiteDrop:
8738           case BlackDrop:
8739             fromX = moveType == WhiteDrop ?
8740               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8741             (int) CharToPiece(ToLower(currentMoveString[0]));
8742             fromY = DROP_RANK;
8743             toX = currentMoveString[2] - AAA;
8744             toY = currentMoveString[3] - ONE;
8745             promoChar = NULLCHAR;
8746             break;
8747           case AmbiguousMove:
8748             /* bug? */
8749             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8750   if (appData.debugMode) {
8751     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8752     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8753     setbuf(debugFP, NULL);
8754   }
8755             DisplayError(buf, 0);
8756             return;
8757           case ImpossibleMove:
8758             /* bug? */
8759             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8760   if (appData.debugMode) {
8761     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8762     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8763     setbuf(debugFP, NULL);
8764   }
8765             DisplayError(buf, 0);
8766             return;
8767           case EndOfFile:
8768             if (boardIndex < backwardMostMove) {
8769                 /* Oops, gap.  How did that happen? */
8770                 DisplayError(_("Gap in move list"), 0);
8771                 return;
8772             }
8773             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8774             if (boardIndex > forwardMostMove) {
8775                 forwardMostMove = boardIndex;
8776             }
8777             return;
8778           case ElapsedTime:
8779             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8780                 strcat(parseList[boardIndex-1], " ");
8781                 strcat(parseList[boardIndex-1], yy_text);
8782             }
8783             continue;
8784           case Comment:
8785           case PGNTag:
8786           case NAG:
8787           default:
8788             /* ignore */
8789             continue;
8790           case WhiteWins:
8791           case BlackWins:
8792           case GameIsDrawn:
8793           case GameUnfinished:
8794             if (gameMode == IcsExamining) {
8795                 if (boardIndex < backwardMostMove) {
8796                     /* Oops, gap.  How did that happen? */
8797                     return;
8798                 }
8799                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8800                 return;
8801             }
8802             gameInfo.result = moveType;
8803             p = strchr(yy_text, '{');
8804             if (p == NULL) p = strchr(yy_text, '(');
8805             if (p == NULL) {
8806                 p = yy_text;
8807                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8808             } else {
8809                 q = strchr(p, *p == '{' ? '}' : ')');
8810                 if (q != NULL) *q = NULLCHAR;
8811                 p++;
8812             }
8813             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8814             gameInfo.resultDetails = StrSave(p);
8815             continue;
8816         }
8817         if (boardIndex >= forwardMostMove &&
8818             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8819             backwardMostMove = blackPlaysFirst ? 1 : 0;
8820             return;
8821         }
8822         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8823                                  fromY, fromX, toY, toX, promoChar,
8824                                  parseList[boardIndex]);
8825         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8826         /* currentMoveString is set as a side-effect of yylex */
8827         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8828         strcat(moveList[boardIndex], "\n");
8829         boardIndex++;
8830         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8831         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8832           case MT_NONE:
8833           case MT_STALEMATE:
8834           default:
8835             break;
8836           case MT_CHECK:
8837             if(gameInfo.variant != VariantShogi)
8838                 strcat(parseList[boardIndex - 1], "+");
8839             break;
8840           case MT_CHECKMATE:
8841           case MT_STAINMATE:
8842             strcat(parseList[boardIndex - 1], "#");
8843             break;
8844         }
8845     }
8846 }
8847
8848
8849 /* Apply a move to the given board  */
8850 void
8851 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8852      int fromX, fromY, toX, toY;
8853      int promoChar;
8854      Board board;
8855 {
8856   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8857   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8858
8859     /* [HGM] compute & store e.p. status and castling rights for new position */
8860     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8861
8862       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8863       oldEP = (signed char)board[EP_STATUS];
8864       board[EP_STATUS] = EP_NONE;
8865
8866       if( board[toY][toX] != EmptySquare )
8867            board[EP_STATUS] = EP_CAPTURE;
8868
8869   if (fromY == DROP_RANK) {
8870         /* must be first */
8871         piece = board[toY][toX] = (ChessSquare) fromX;
8872   } else {
8873       int i;
8874
8875       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8876            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8877                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8878       } else
8879       if( board[fromY][fromX] == WhitePawn ) {
8880            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8881                board[EP_STATUS] = EP_PAWN_MOVE;
8882            if( toY-fromY==2) {
8883                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8884                         gameInfo.variant != VariantBerolina || toX < fromX)
8885                       board[EP_STATUS] = toX | berolina;
8886                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8887                         gameInfo.variant != VariantBerolina || toX > fromX)
8888                       board[EP_STATUS] = toX;
8889            }
8890       } else
8891       if( board[fromY][fromX] == BlackPawn ) {
8892            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8893                board[EP_STATUS] = EP_PAWN_MOVE;
8894            if( toY-fromY== -2) {
8895                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8896                         gameInfo.variant != VariantBerolina || toX < fromX)
8897                       board[EP_STATUS] = toX | berolina;
8898                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8899                         gameInfo.variant != VariantBerolina || toX > fromX)
8900                       board[EP_STATUS] = toX;
8901            }
8902        }
8903
8904        for(i=0; i<nrCastlingRights; i++) {
8905            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8906               board[CASTLING][i] == toX   && castlingRank[i] == toY
8907              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8908        }
8909
8910      if (fromX == toX && fromY == toY) return;
8911
8912      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8913      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8914      if(gameInfo.variant == VariantKnightmate)
8915          king += (int) WhiteUnicorn - (int) WhiteKing;
8916
8917     /* Code added by Tord: */
8918     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8919     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8920         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8921       board[fromY][fromX] = EmptySquare;
8922       board[toY][toX] = EmptySquare;
8923       if((toX > fromX) != (piece == WhiteRook)) {
8924         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8925       } else {
8926         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8927       }
8928     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8929                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8930       board[fromY][fromX] = EmptySquare;
8931       board[toY][toX] = EmptySquare;
8932       if((toX > fromX) != (piece == BlackRook)) {
8933         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8934       } else {
8935         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8936       }
8937     /* End of code added by Tord */
8938
8939     } else if (board[fromY][fromX] == king
8940         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8941         && toY == fromY && toX > fromX+1) {
8942         board[fromY][fromX] = EmptySquare;
8943         board[toY][toX] = king;
8944         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8945         board[fromY][BOARD_RGHT-1] = EmptySquare;
8946     } else if (board[fromY][fromX] == king
8947         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8948                && toY == fromY && toX < fromX-1) {
8949         board[fromY][fromX] = EmptySquare;
8950         board[toY][toX] = king;
8951         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8952         board[fromY][BOARD_LEFT] = EmptySquare;
8953     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8954                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8955                && toY >= BOARD_HEIGHT-promoRank
8956                ) {
8957         /* white pawn promotion */
8958         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8959         if (board[toY][toX] == EmptySquare) {
8960             board[toY][toX] = WhiteQueen;
8961         }
8962         if(gameInfo.variant==VariantBughouse ||
8963            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8964             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8965         board[fromY][fromX] = EmptySquare;
8966     } else if ((fromY == BOARD_HEIGHT-4)
8967                && (toX != fromX)
8968                && gameInfo.variant != VariantXiangqi
8969                && gameInfo.variant != VariantBerolina
8970                && (board[fromY][fromX] == WhitePawn)
8971                && (board[toY][toX] == EmptySquare)) {
8972         board[fromY][fromX] = EmptySquare;
8973         board[toY][toX] = WhitePawn;
8974         captured = board[toY - 1][toX];
8975         board[toY - 1][toX] = EmptySquare;
8976     } else if ((fromY == BOARD_HEIGHT-4)
8977                && (toX == fromX)
8978                && gameInfo.variant == VariantBerolina
8979                && (board[fromY][fromX] == WhitePawn)
8980                && (board[toY][toX] == EmptySquare)) {
8981         board[fromY][fromX] = EmptySquare;
8982         board[toY][toX] = WhitePawn;
8983         if(oldEP & EP_BEROLIN_A) {
8984                 captured = board[fromY][fromX-1];
8985                 board[fromY][fromX-1] = EmptySquare;
8986         }else{  captured = board[fromY][fromX+1];
8987                 board[fromY][fromX+1] = EmptySquare;
8988         }
8989     } else if (board[fromY][fromX] == king
8990         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8991                && toY == fromY && toX > fromX+1) {
8992         board[fromY][fromX] = EmptySquare;
8993         board[toY][toX] = king;
8994         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8995         board[fromY][BOARD_RGHT-1] = EmptySquare;
8996     } else if (board[fromY][fromX] == king
8997         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8998                && toY == fromY && toX < fromX-1) {
8999         board[fromY][fromX] = EmptySquare;
9000         board[toY][toX] = king;
9001         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9002         board[fromY][BOARD_LEFT] = EmptySquare;
9003     } else if (fromY == 7 && fromX == 3
9004                && board[fromY][fromX] == BlackKing
9005                && toY == 7 && toX == 5) {
9006         board[fromY][fromX] = EmptySquare;
9007         board[toY][toX] = BlackKing;
9008         board[fromY][7] = EmptySquare;
9009         board[toY][4] = BlackRook;
9010     } else if (fromY == 7 && fromX == 3
9011                && board[fromY][fromX] == BlackKing
9012                && toY == 7 && toX == 1) {
9013         board[fromY][fromX] = EmptySquare;
9014         board[toY][toX] = BlackKing;
9015         board[fromY][0] = EmptySquare;
9016         board[toY][2] = BlackRook;
9017     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9018                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9019                && toY < promoRank
9020                ) {
9021         /* black pawn promotion */
9022         board[toY][toX] = CharToPiece(ToLower(promoChar));
9023         if (board[toY][toX] == EmptySquare) {
9024             board[toY][toX] = BlackQueen;
9025         }
9026         if(gameInfo.variant==VariantBughouse ||
9027            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9028             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9029         board[fromY][fromX] = EmptySquare;
9030     } else if ((fromY == 3)
9031                && (toX != fromX)
9032                && gameInfo.variant != VariantXiangqi
9033                && gameInfo.variant != VariantBerolina
9034                && (board[fromY][fromX] == BlackPawn)
9035                && (board[toY][toX] == EmptySquare)) {
9036         board[fromY][fromX] = EmptySquare;
9037         board[toY][toX] = BlackPawn;
9038         captured = board[toY + 1][toX];
9039         board[toY + 1][toX] = EmptySquare;
9040     } else if ((fromY == 3)
9041                && (toX == fromX)
9042                && gameInfo.variant == VariantBerolina
9043                && (board[fromY][fromX] == BlackPawn)
9044                && (board[toY][toX] == EmptySquare)) {
9045         board[fromY][fromX] = EmptySquare;
9046         board[toY][toX] = BlackPawn;
9047         if(oldEP & EP_BEROLIN_A) {
9048                 captured = board[fromY][fromX-1];
9049                 board[fromY][fromX-1] = EmptySquare;
9050         }else{  captured = board[fromY][fromX+1];
9051                 board[fromY][fromX+1] = EmptySquare;
9052         }
9053     } else {
9054         board[toY][toX] = board[fromY][fromX];
9055         board[fromY][fromX] = EmptySquare;
9056     }
9057   }
9058
9059     if (gameInfo.holdingsWidth != 0) {
9060
9061       /* !!A lot more code needs to be written to support holdings  */
9062       /* [HGM] OK, so I have written it. Holdings are stored in the */
9063       /* penultimate board files, so they are automaticlly stored   */
9064       /* in the game history.                                       */
9065       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9066                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9067         /* Delete from holdings, by decreasing count */
9068         /* and erasing image if necessary            */
9069         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9070         if(p < (int) BlackPawn) { /* white drop */
9071              p -= (int)WhitePawn;
9072                  p = PieceToNumber((ChessSquare)p);
9073              if(p >= gameInfo.holdingsSize) p = 0;
9074              if(--board[p][BOARD_WIDTH-2] <= 0)
9075                   board[p][BOARD_WIDTH-1] = EmptySquare;
9076              if((int)board[p][BOARD_WIDTH-2] < 0)
9077                         board[p][BOARD_WIDTH-2] = 0;
9078         } else {                  /* black drop */
9079              p -= (int)BlackPawn;
9080                  p = PieceToNumber((ChessSquare)p);
9081              if(p >= gameInfo.holdingsSize) p = 0;
9082              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9083                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9084              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9085                         board[BOARD_HEIGHT-1-p][1] = 0;
9086         }
9087       }
9088       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9089           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9090         /* [HGM] holdings: Add to holdings, if holdings exist */
9091         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9092                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9093                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9094         }
9095         p = (int) captured;
9096         if (p >= (int) BlackPawn) {
9097           p -= (int)BlackPawn;
9098           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9099                   /* in Shogi restore piece to its original  first */
9100                   captured = (ChessSquare) (DEMOTED captured);
9101                   p = DEMOTED p;
9102           }
9103           p = PieceToNumber((ChessSquare)p);
9104           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9105           board[p][BOARD_WIDTH-2]++;
9106           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9107         } else {
9108           p -= (int)WhitePawn;
9109           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9110                   captured = (ChessSquare) (DEMOTED captured);
9111                   p = DEMOTED p;
9112           }
9113           p = PieceToNumber((ChessSquare)p);
9114           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9115           board[BOARD_HEIGHT-1-p][1]++;
9116           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9117         }
9118       }
9119     } else if (gameInfo.variant == VariantAtomic) {
9120       if (captured != EmptySquare) {
9121         int y, x;
9122         for (y = toY-1; y <= toY+1; y++) {
9123           for (x = toX-1; x <= toX+1; x++) {
9124             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9125                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9126               board[y][x] = EmptySquare;
9127             }
9128           }
9129         }
9130         board[toY][toX] = EmptySquare;
9131       }
9132     }
9133     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9134         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9135     } else
9136     if(promoChar == '+') {
9137         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9138         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9139     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9140         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9141     }
9142     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9143                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9144         // [HGM] superchess: take promotion piece out of holdings
9145         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9146         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9147             if(!--board[k][BOARD_WIDTH-2])
9148                 board[k][BOARD_WIDTH-1] = EmptySquare;
9149         } else {
9150             if(!--board[BOARD_HEIGHT-1-k][1])
9151                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9152         }
9153     }
9154
9155 }
9156
9157 /* Updates forwardMostMove */
9158 void
9159 MakeMove(fromX, fromY, toX, toY, promoChar)
9160      int fromX, fromY, toX, toY;
9161      int promoChar;
9162 {
9163 //    forwardMostMove++; // [HGM] bare: moved downstream
9164
9165     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9166         int timeLeft; static int lastLoadFlag=0; int king, piece;
9167         piece = boards[forwardMostMove][fromY][fromX];
9168         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9169         if(gameInfo.variant == VariantKnightmate)
9170             king += (int) WhiteUnicorn - (int) WhiteKing;
9171         if(forwardMostMove == 0) {
9172             if(blackPlaysFirst)
9173                 fprintf(serverMoves, "%s;", second.tidy);
9174             fprintf(serverMoves, "%s;", first.tidy);
9175             if(!blackPlaysFirst)
9176                 fprintf(serverMoves, "%s;", second.tidy);
9177         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9178         lastLoadFlag = loadFlag;
9179         // print base move
9180         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9181         // print castling suffix
9182         if( toY == fromY && piece == king ) {
9183             if(toX-fromX > 1)
9184                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9185             if(fromX-toX >1)
9186                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9187         }
9188         // e.p. suffix
9189         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9190              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9191              boards[forwardMostMove][toY][toX] == EmptySquare
9192              && fromX != toX && fromY != toY)
9193                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9194         // promotion suffix
9195         if(promoChar != NULLCHAR)
9196                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9197         if(!loadFlag) {
9198             fprintf(serverMoves, "/%d/%d",
9199                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9200             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9201             else                      timeLeft = blackTimeRemaining/1000;
9202             fprintf(serverMoves, "/%d", timeLeft);
9203         }
9204         fflush(serverMoves);
9205     }
9206
9207     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9208       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9209                         0, 1);
9210       return;
9211     }
9212     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9213     if (commentList[forwardMostMove+1] != NULL) {
9214         free(commentList[forwardMostMove+1]);
9215         commentList[forwardMostMove+1] = NULL;
9216     }
9217     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9218     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9219     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9220     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9221     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9222     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9223     gameInfo.result = GameUnfinished;
9224     if (gameInfo.resultDetails != NULL) {
9225         free(gameInfo.resultDetails);
9226         gameInfo.resultDetails = NULL;
9227     }
9228     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9229                               moveList[forwardMostMove - 1]);
9230     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9231                              PosFlags(forwardMostMove - 1),
9232                              fromY, fromX, toY, toX, promoChar,
9233                              parseList[forwardMostMove - 1]);
9234     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9235       case MT_NONE:
9236       case MT_STALEMATE:
9237       default:
9238         break;
9239       case MT_CHECK:
9240         if(gameInfo.variant != VariantShogi)
9241             strcat(parseList[forwardMostMove - 1], "+");
9242         break;
9243       case MT_CHECKMATE:
9244       case MT_STAINMATE:
9245         strcat(parseList[forwardMostMove - 1], "#");
9246         break;
9247     }
9248     if (appData.debugMode) {
9249         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9250     }
9251
9252 }
9253
9254 /* Updates currentMove if not pausing */
9255 void
9256 ShowMove(fromX, fromY, toX, toY)
9257 {
9258     int instant = (gameMode == PlayFromGameFile) ?
9259         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9260     if(appData.noGUI) return;
9261     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9262         if (!instant) {
9263             if (forwardMostMove == currentMove + 1) {
9264                 AnimateMove(boards[forwardMostMove - 1],
9265                             fromX, fromY, toX, toY);
9266             }
9267             if (appData.highlightLastMove) {
9268                 SetHighlights(fromX, fromY, toX, toY);
9269             }
9270         }
9271         currentMove = forwardMostMove;
9272     }
9273
9274     if (instant) return;
9275
9276     DisplayMove(currentMove - 1);
9277     DrawPosition(FALSE, boards[currentMove]);
9278     DisplayBothClocks();
9279     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9280     DisplayBook(currentMove);
9281 }
9282
9283 void SendEgtPath(ChessProgramState *cps)
9284 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9285         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9286
9287         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9288
9289         while(*p) {
9290             char c, *q = name+1, *r, *s;
9291
9292             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9293             while(*p && *p != ',') *q++ = *p++;
9294             *q++ = ':'; *q = 0;
9295             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9296                 strcmp(name, ",nalimov:") == 0 ) {
9297                 // take nalimov path from the menu-changeable option first, if it is defined
9298               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9299                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9300             } else
9301             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9302                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9303                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9304                 s = r = StrStr(s, ":") + 1; // beginning of path info
9305                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9306                 c = *r; *r = 0;             // temporarily null-terminate path info
9307                     *--q = 0;               // strip of trailig ':' from name
9308                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9309                 *r = c;
9310                 SendToProgram(buf,cps);     // send egtbpath command for this format
9311             }
9312             if(*p == ',') p++; // read away comma to position for next format name
9313         }
9314 }
9315
9316 void
9317 InitChessProgram(cps, setup)
9318      ChessProgramState *cps;
9319      int setup; /* [HGM] needed to setup FRC opening position */
9320 {
9321     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9322     if (appData.noChessProgram) return;
9323     hintRequested = FALSE;
9324     bookRequested = FALSE;
9325
9326     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9327     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9328     if(cps->memSize) { /* [HGM] memory */
9329       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9330         SendToProgram(buf, cps);
9331     }
9332     SendEgtPath(cps); /* [HGM] EGT */
9333     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9334       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9335         SendToProgram(buf, cps);
9336     }
9337
9338     SendToProgram(cps->initString, cps);
9339     if (gameInfo.variant != VariantNormal &&
9340         gameInfo.variant != VariantLoadable
9341         /* [HGM] also send variant if board size non-standard */
9342         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9343                                             ) {
9344       char *v = VariantName(gameInfo.variant);
9345       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9346         /* [HGM] in protocol 1 we have to assume all variants valid */
9347         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9348         DisplayFatalError(buf, 0, 1);
9349         return;
9350       }
9351
9352       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9353       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9354       if( gameInfo.variant == VariantXiangqi )
9355            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9356       if( gameInfo.variant == VariantShogi )
9357            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9358       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9359            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9360       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9361           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9362            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9363       if( gameInfo.variant == VariantCourier )
9364            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9365       if( gameInfo.variant == VariantSuper )
9366            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9367       if( gameInfo.variant == VariantGreat )
9368            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9369       if( gameInfo.variant == VariantSChess )
9370            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9371
9372       if(overruled) {
9373         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9374                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9375            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9376            if(StrStr(cps->variants, b) == NULL) {
9377                // specific sized variant not known, check if general sizing allowed
9378                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9379                    if(StrStr(cps->variants, "boardsize") == NULL) {
9380                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9381                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9382                        DisplayFatalError(buf, 0, 1);
9383                        return;
9384                    }
9385                    /* [HGM] here we really should compare with the maximum supported board size */
9386                }
9387            }
9388       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9389       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9390       SendToProgram(buf, cps);
9391     }
9392     currentlyInitializedVariant = gameInfo.variant;
9393
9394     /* [HGM] send opening position in FRC to first engine */
9395     if(setup) {
9396           SendToProgram("force\n", cps);
9397           SendBoard(cps, 0);
9398           /* engine is now in force mode! Set flag to wake it up after first move. */
9399           setboardSpoiledMachineBlack = 1;
9400     }
9401
9402     if (cps->sendICS) {
9403       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9404       SendToProgram(buf, cps);
9405     }
9406     cps->maybeThinking = FALSE;
9407     cps->offeredDraw = 0;
9408     if (!appData.icsActive) {
9409         SendTimeControl(cps, movesPerSession, timeControl,
9410                         timeIncrement, appData.searchDepth,
9411                         searchTime);
9412     }
9413     if (appData.showThinking
9414         // [HGM] thinking: four options require thinking output to be sent
9415         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9416                                 ) {
9417         SendToProgram("post\n", cps);
9418     }
9419     SendToProgram("hard\n", cps);
9420     if (!appData.ponderNextMove) {
9421         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9422            it without being sure what state we are in first.  "hard"
9423            is not a toggle, so that one is OK.
9424          */
9425         SendToProgram("easy\n", cps);
9426     }
9427     if (cps->usePing) {
9428       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9429       SendToProgram(buf, cps);
9430     }
9431     cps->initDone = TRUE;
9432 }
9433
9434
9435 void
9436 StartChessProgram(cps)
9437      ChessProgramState *cps;
9438 {
9439     char buf[MSG_SIZ];
9440     int err;
9441
9442     if (appData.noChessProgram) return;
9443     cps->initDone = FALSE;
9444
9445     if (strcmp(cps->host, "localhost") == 0) {
9446         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9447     } else if (*appData.remoteShell == NULLCHAR) {
9448         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9449     } else {
9450         if (*appData.remoteUser == NULLCHAR) {
9451           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9452                     cps->program);
9453         } else {
9454           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9455                     cps->host, appData.remoteUser, cps->program);
9456         }
9457         err = StartChildProcess(buf, "", &cps->pr);
9458     }
9459
9460     if (err != 0) {
9461       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9462         DisplayFatalError(buf, err, 1);
9463         cps->pr = NoProc;
9464         cps->isr = NULL;
9465         return;
9466     }
9467
9468     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9469     if (cps->protocolVersion > 1) {
9470       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9471       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9472       cps->comboCnt = 0;  //                and values of combo boxes
9473       SendToProgram(buf, cps);
9474     } else {
9475       SendToProgram("xboard\n", cps);
9476     }
9477 }
9478
9479 void
9480 TwoMachinesEventIfReady P((void))
9481 {
9482   static int curMess = 0;
9483   if (first.lastPing != first.lastPong) {
9484     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9485     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9486     return;
9487   }
9488   if (second.lastPing != second.lastPong) {
9489     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9490     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9491     return;
9492   }
9493   DisplayMessage("", ""); curMess = 0;
9494   ThawUI();
9495   TwoMachinesEvent();
9496 }
9497
9498 int
9499 CreateTourney(char *name)
9500 {
9501         FILE *f;
9502         if(name[0] == NULLCHAR) return 0;
9503         f = fopen(appData.tourneyFile, "r");
9504         if(f) { // file exists
9505             ParseArgsFromFile(f); // parse it
9506         } else {
9507             f = fopen(appData.tourneyFile, "w");
9508             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9509                 // create a file with tournament description
9510                 fprintf(f, "-participants {%s}\n", appData.participants);
9511                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9512                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9513                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9514                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9515                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9516                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9517                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9518                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9519                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9520                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9521                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9522                 if(searchTime > 0)
9523                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9524                 else {
9525                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9526                         fprintf(f, "-tc %s\n", appData.timeControl);
9527                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9528                 }
9529                 fprintf(f, "-results \"\"\n");
9530             }
9531         }
9532         fclose(f);
9533         appData.noChessProgram = FALSE;
9534         appData.clockMode = TRUE;
9535         SetGNUMode();
9536         return 1;
9537 }
9538
9539 #define MAXENGINES 1000
9540 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9541
9542 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9543 {
9544     char buf[MSG_SIZ], *p, *q;
9545     int i=1;
9546     while(*names) {
9547         p = names; q = buf;
9548         while(*p && *p != '\n') *q++ = *p++;
9549         *q = 0;
9550         if(engineList[i]) free(engineList[i]);
9551         engineList[i] = strdup(buf);
9552         if(*p == '\n') p++;
9553         TidyProgramName(engineList[i], "localhost", buf);
9554         if(engineMnemonic[i]) free(engineMnemonic[i]);
9555         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9556             strcat(buf, " (");
9557             sscanf(q + 8, "%s", buf + strlen(buf));
9558             strcat(buf, ")");
9559         }
9560         engineMnemonic[i] = strdup(buf);
9561         names = p; i++;
9562       if(i > MAXENGINES - 2) break;
9563     }
9564     engineList[i] = NULL;
9565 }
9566
9567 // following implemented as macro to avoid type limitations
9568 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9569
9570 void SwapEngines(int n)
9571 {   // swap settings for first engine and other engine (so far only some selected options)
9572     int h;
9573     char *p;
9574     if(n == 0) return;
9575     SWAP(directory, p)
9576     SWAP(chessProgram, p)
9577     SWAP(isUCI, h)
9578     SWAP(hasOwnBookUCI, h)
9579     SWAP(protocolVersion, h)
9580     SWAP(reuse, h)
9581     SWAP(scoreIsAbsolute, h)
9582     SWAP(timeOdds, h)
9583     SWAP(logo, p)
9584     SWAP(pgnName, p)
9585 }
9586
9587 void
9588 SetPlayer(int player)
9589 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9590     int i;
9591     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9592     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9593     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9594     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9595     if(mnemonic[i]) {
9596         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9597         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9598         ParseArgsFromString(buf);
9599     }
9600     free(engineName);
9601 }
9602
9603 int
9604 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9605 {   // determine players from game number
9606     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9607
9608     if(appData.tourneyType == 0) {
9609         roundsPerCycle = (nPlayers - 1) | 1;
9610         pairingsPerRound = nPlayers / 2;
9611     } else if(appData.tourneyType > 0) {
9612         roundsPerCycle = nPlayers - appData.tourneyType;
9613         pairingsPerRound = appData.tourneyType;
9614     }
9615     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9616     gamesPerCycle = gamesPerRound * roundsPerCycle;
9617     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9618     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9619     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9620     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9621     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9622     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9623
9624     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9625     if(appData.roundSync) *syncInterval = gamesPerRound;
9626
9627     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9628
9629     if(appData.tourneyType == 0) {
9630         if(curPairing == (nPlayers-1)/2 ) {
9631             *whitePlayer = curRound;
9632             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9633         } else {
9634             *whitePlayer = curRound - pairingsPerRound + curPairing;
9635             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9636             *blackPlayer = curRound + pairingsPerRound - curPairing;
9637             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9638         }
9639     } else if(appData.tourneyType > 0) {
9640         *whitePlayer = curPairing;
9641         *blackPlayer = curRound + appData.tourneyType;
9642     }
9643
9644     // take care of white/black alternation per round. 
9645     // For cycles and games this is already taken care of by default, derived from matchGame!
9646     return curRound & 1;
9647 }
9648
9649 int
9650 NextTourneyGame(int nr, int *swapColors)
9651 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9652     char *p, *q;
9653     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9654     FILE *tf;
9655     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9656     tf = fopen(appData.tourneyFile, "r");
9657     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9658     ParseArgsFromFile(tf); fclose(tf);
9659     InitTimeControls(); // TC might be altered from tourney file
9660
9661     p = appData.participants;
9662     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9663     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9664
9665     if(syncInterval) {
9666         p = q = appData.results;
9667         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9668         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9669             DisplayMessage(_("Waiting for other game(s)"),"");
9670             waitingForGame = TRUE;
9671             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9672             return 0;
9673         }
9674         waitingForGame = FALSE;
9675     }
9676
9677     if(first.pr != NoProc) return 1; // engines already loaded
9678
9679     // redefine engines, engine dir, etc.
9680     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9681     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9682     SwapEngines(1);
9683     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9684     SwapEngines(1);         // and make that valid for second engine by swapping
9685     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9686     InitEngine(&second, 1);
9687     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9688     return 1;
9689 }
9690
9691 void
9692 NextMatchGame()
9693 {   // performs game initialization that does not invoke engines, and then tries to start the game
9694     int firstWhite, swapColors = 0;
9695     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9696     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9697     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9698     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9699     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9700     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9701     Reset(FALSE, first.pr != NoProc);
9702     appData.noChessProgram = FALSE;
9703     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9704     TwoMachinesEvent();
9705 }
9706
9707 void UserAdjudicationEvent( int result )
9708 {
9709     ChessMove gameResult = GameIsDrawn;
9710
9711     if( result > 0 ) {
9712         gameResult = WhiteWins;
9713     }
9714     else if( result < 0 ) {
9715         gameResult = BlackWins;
9716     }
9717
9718     if( gameMode == TwoMachinesPlay ) {
9719         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9720     }
9721 }
9722
9723
9724 // [HGM] save: calculate checksum of game to make games easily identifiable
9725 int StringCheckSum(char *s)
9726 {
9727         int i = 0;
9728         if(s==NULL) return 0;
9729         while(*s) i = i*259 + *s++;
9730         return i;
9731 }
9732
9733 int GameCheckSum()
9734 {
9735         int i, sum=0;
9736         for(i=backwardMostMove; i<forwardMostMove; i++) {
9737                 sum += pvInfoList[i].depth;
9738                 sum += StringCheckSum(parseList[i]);
9739                 sum += StringCheckSum(commentList[i]);
9740                 sum *= 261;
9741         }
9742         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9743         return sum + StringCheckSum(commentList[i]);
9744 } // end of save patch
9745
9746 void
9747 GameEnds(result, resultDetails, whosays)
9748      ChessMove result;
9749      char *resultDetails;
9750      int whosays;
9751 {
9752     GameMode nextGameMode;
9753     int isIcsGame;
9754     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9755
9756     if(endingGame) return; /* [HGM] crash: forbid recursion */
9757     endingGame = 1;
9758     if(twoBoards) { // [HGM] dual: switch back to one board
9759         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9760         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9761     }
9762     if (appData.debugMode) {
9763       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9764               result, resultDetails ? resultDetails : "(null)", whosays);
9765     }
9766
9767     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9768
9769     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9770         /* If we are playing on ICS, the server decides when the
9771            game is over, but the engine can offer to draw, claim
9772            a draw, or resign.
9773          */
9774 #if ZIPPY
9775         if (appData.zippyPlay && first.initDone) {
9776             if (result == GameIsDrawn) {
9777                 /* In case draw still needs to be claimed */
9778                 SendToICS(ics_prefix);
9779                 SendToICS("draw\n");
9780             } else if (StrCaseStr(resultDetails, "resign")) {
9781                 SendToICS(ics_prefix);
9782                 SendToICS("resign\n");
9783             }
9784         }
9785 #endif
9786         endingGame = 0; /* [HGM] crash */
9787         return;
9788     }
9789
9790     /* If we're loading the game from a file, stop */
9791     if (whosays == GE_FILE) {
9792       (void) StopLoadGameTimer();
9793       gameFileFP = NULL;
9794     }
9795
9796     /* Cancel draw offers */
9797     first.offeredDraw = second.offeredDraw = 0;
9798
9799     /* If this is an ICS game, only ICS can really say it's done;
9800        if not, anyone can. */
9801     isIcsGame = (gameMode == IcsPlayingWhite ||
9802                  gameMode == IcsPlayingBlack ||
9803                  gameMode == IcsObserving    ||
9804                  gameMode == IcsExamining);
9805
9806     if (!isIcsGame || whosays == GE_ICS) {
9807         /* OK -- not an ICS game, or ICS said it was done */
9808         StopClocks();
9809         if (!isIcsGame && !appData.noChessProgram)
9810           SetUserThinkingEnables();
9811
9812         /* [HGM] if a machine claims the game end we verify this claim */
9813         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9814             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9815                 char claimer;
9816                 ChessMove trueResult = (ChessMove) -1;
9817
9818                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9819                                             first.twoMachinesColor[0] :
9820                                             second.twoMachinesColor[0] ;
9821
9822                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9823                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9824                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9825                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9826                 } else
9827                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9828                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9829                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9830                 } else
9831                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9832                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9833                 }
9834
9835                 // now verify win claims, but not in drop games, as we don't understand those yet
9836                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9837                                                  || gameInfo.variant == VariantGreat) &&
9838                     (result == WhiteWins && claimer == 'w' ||
9839                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9840                       if (appData.debugMode) {
9841                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9842                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9843                       }
9844                       if(result != trueResult) {
9845                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9846                               result = claimer == 'w' ? BlackWins : WhiteWins;
9847                               resultDetails = buf;
9848                       }
9849                 } else
9850                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9851                     && (forwardMostMove <= backwardMostMove ||
9852                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9853                         (claimer=='b')==(forwardMostMove&1))
9854                                                                                   ) {
9855                       /* [HGM] verify: draws that were not flagged are false claims */
9856                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9857                       result = claimer == 'w' ? BlackWins : WhiteWins;
9858                       resultDetails = buf;
9859                 }
9860                 /* (Claiming a loss is accepted no questions asked!) */
9861             }
9862             /* [HGM] bare: don't allow bare King to win */
9863             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9864                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9865                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9866                && result != GameIsDrawn)
9867             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9868                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9869                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9870                         if(p >= 0 && p <= (int)WhiteKing) k++;
9871                 }
9872                 if (appData.debugMode) {
9873                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9874                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9875                 }
9876                 if(k <= 1) {
9877                         result = GameIsDrawn;
9878                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9879                         resultDetails = buf;
9880                 }
9881             }
9882         }
9883
9884
9885         if(serverMoves != NULL && !loadFlag) { char c = '=';
9886             if(result==WhiteWins) c = '+';
9887             if(result==BlackWins) c = '-';
9888             if(resultDetails != NULL)
9889                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9890         }
9891         if (resultDetails != NULL) {
9892             gameInfo.result = result;
9893             gameInfo.resultDetails = StrSave(resultDetails);
9894
9895             /* display last move only if game was not loaded from file */
9896             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9897                 DisplayMove(currentMove - 1);
9898
9899             if (forwardMostMove != 0) {
9900                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9901                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9902                                                                 ) {
9903                     if (*appData.saveGameFile != NULLCHAR) {
9904                         SaveGameToFile(appData.saveGameFile, TRUE);
9905                     } else if (appData.autoSaveGames) {
9906                         AutoSaveGame();
9907                     }
9908                     if (*appData.savePositionFile != NULLCHAR) {
9909                         SavePositionToFile(appData.savePositionFile);
9910                     }
9911                 }
9912             }
9913
9914             /* Tell program how game ended in case it is learning */
9915             /* [HGM] Moved this to after saving the PGN, just in case */
9916             /* engine died and we got here through time loss. In that */
9917             /* case we will get a fatal error writing the pipe, which */
9918             /* would otherwise lose us the PGN.                       */
9919             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9920             /* output during GameEnds should never be fatal anymore   */
9921             if (gameMode == MachinePlaysWhite ||
9922                 gameMode == MachinePlaysBlack ||
9923                 gameMode == TwoMachinesPlay ||
9924                 gameMode == IcsPlayingWhite ||
9925                 gameMode == IcsPlayingBlack ||
9926                 gameMode == BeginningOfGame) {
9927                 char buf[MSG_SIZ];
9928                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9929                         resultDetails);
9930                 if (first.pr != NoProc) {
9931                     SendToProgram(buf, &first);
9932                 }
9933                 if (second.pr != NoProc &&
9934                     gameMode == TwoMachinesPlay) {
9935                     SendToProgram(buf, &second);
9936                 }
9937             }
9938         }
9939
9940         if (appData.icsActive) {
9941             if (appData.quietPlay &&
9942                 (gameMode == IcsPlayingWhite ||
9943                  gameMode == IcsPlayingBlack)) {
9944                 SendToICS(ics_prefix);
9945                 SendToICS("set shout 1\n");
9946             }
9947             nextGameMode = IcsIdle;
9948             ics_user_moved = FALSE;
9949             /* clean up premove.  It's ugly when the game has ended and the
9950              * premove highlights are still on the board.
9951              */
9952             if (gotPremove) {
9953               gotPremove = FALSE;
9954               ClearPremoveHighlights();
9955               DrawPosition(FALSE, boards[currentMove]);
9956             }
9957             if (whosays == GE_ICS) {
9958                 switch (result) {
9959                 case WhiteWins:
9960                     if (gameMode == IcsPlayingWhite)
9961                         PlayIcsWinSound();
9962                     else if(gameMode == IcsPlayingBlack)
9963                         PlayIcsLossSound();
9964                     break;
9965                 case BlackWins:
9966                     if (gameMode == IcsPlayingBlack)
9967                         PlayIcsWinSound();
9968                     else if(gameMode == IcsPlayingWhite)
9969                         PlayIcsLossSound();
9970                     break;
9971                 case GameIsDrawn:
9972                     PlayIcsDrawSound();
9973                     break;
9974                 default:
9975                     PlayIcsUnfinishedSound();
9976                 }
9977             }
9978         } else if (gameMode == EditGame ||
9979                    gameMode == PlayFromGameFile ||
9980                    gameMode == AnalyzeMode ||
9981                    gameMode == AnalyzeFile) {
9982             nextGameMode = gameMode;
9983         } else {
9984             nextGameMode = EndOfGame;
9985         }
9986         pausing = FALSE;
9987         ModeHighlight();
9988     } else {
9989         nextGameMode = gameMode;
9990     }
9991
9992     if (appData.noChessProgram) {
9993         gameMode = nextGameMode;
9994         ModeHighlight();
9995         endingGame = 0; /* [HGM] crash */
9996         return;
9997     }
9998
9999     if (first.reuse) {
10000         /* Put first chess program into idle state */
10001         if (first.pr != NoProc &&
10002             (gameMode == MachinePlaysWhite ||
10003              gameMode == MachinePlaysBlack ||
10004              gameMode == TwoMachinesPlay ||
10005              gameMode == IcsPlayingWhite ||
10006              gameMode == IcsPlayingBlack ||
10007              gameMode == BeginningOfGame)) {
10008             SendToProgram("force\n", &first);
10009             if (first.usePing) {
10010               char buf[MSG_SIZ];
10011               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10012               SendToProgram(buf, &first);
10013             }
10014         }
10015     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10016         /* Kill off first chess program */
10017         if (first.isr != NULL)
10018           RemoveInputSource(first.isr);
10019         first.isr = NULL;
10020
10021         if (first.pr != NoProc) {
10022             ExitAnalyzeMode();
10023             DoSleep( appData.delayBeforeQuit );
10024             SendToProgram("quit\n", &first);
10025             DoSleep( appData.delayAfterQuit );
10026             DestroyChildProcess(first.pr, first.useSigterm);
10027         }
10028         first.pr = NoProc;
10029     }
10030     if (second.reuse) {
10031         /* Put second chess program into idle state */
10032         if (second.pr != NoProc &&
10033             gameMode == TwoMachinesPlay) {
10034             SendToProgram("force\n", &second);
10035             if (second.usePing) {
10036               char buf[MSG_SIZ];
10037               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10038               SendToProgram(buf, &second);
10039             }
10040         }
10041     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10042         /* Kill off second chess program */
10043         if (second.isr != NULL)
10044           RemoveInputSource(second.isr);
10045         second.isr = NULL;
10046
10047         if (second.pr != NoProc) {
10048             DoSleep( appData.delayBeforeQuit );
10049             SendToProgram("quit\n", &second);
10050             DoSleep( appData.delayAfterQuit );
10051             DestroyChildProcess(second.pr, second.useSigterm);
10052         }
10053         second.pr = NoProc;
10054     }
10055
10056     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10057         char resChar = '=';
10058         switch (result) {
10059         case WhiteWins:
10060           resChar = '+';
10061           if (first.twoMachinesColor[0] == 'w') {
10062             first.matchWins++;
10063           } else {
10064             second.matchWins++;
10065           }
10066           break;
10067         case BlackWins:
10068           resChar = '-';
10069           if (first.twoMachinesColor[0] == 'b') {
10070             first.matchWins++;
10071           } else {
10072             second.matchWins++;
10073           }
10074           break;
10075         case GameUnfinished:
10076           resChar = ' ';
10077         default:
10078           break;
10079         }
10080
10081         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10082         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10083             ReserveGame(nextGame, resChar); // sets nextGame
10084             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10085         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10086
10087         if (nextGame <= appData.matchGames && !abortMatch) {
10088             gameMode = nextGameMode;
10089             matchGame = nextGame; // this will be overruled in tourney mode!
10090             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10091             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10092             endingGame = 0; /* [HGM] crash */
10093             return;
10094         } else {
10095             gameMode = nextGameMode;
10096             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10097                      first.tidy, second.tidy,
10098                      first.matchWins, second.matchWins,
10099                      appData.matchGames - (first.matchWins + second.matchWins));
10100             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10101             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10102                 first.twoMachinesColor = "black\n";
10103                 second.twoMachinesColor = "white\n";
10104             } else {
10105                 first.twoMachinesColor = "white\n";
10106                 second.twoMachinesColor = "black\n";
10107             }
10108         }
10109     }
10110     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10111         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10112       ExitAnalyzeMode();
10113     gameMode = nextGameMode;
10114     ModeHighlight();
10115     endingGame = 0;  /* [HGM] crash */
10116     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10117         if(matchMode == TRUE) { // match through command line: exit with or without popup
10118             if(ranking) {
10119                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10120                 else ExitEvent(0);
10121             } else DisplayFatalError(buf, 0, 0);
10122         } else { // match through menu; just stop, with or without popup
10123             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10124             if(ranking){
10125                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10126             } else DisplayNote(buf);
10127       }
10128       if(ranking) free(ranking);
10129     }
10130 }
10131
10132 /* Assumes program was just initialized (initString sent).
10133    Leaves program in force mode. */
10134 void
10135 FeedMovesToProgram(cps, upto)
10136      ChessProgramState *cps;
10137      int upto;
10138 {
10139     int i;
10140
10141     if (appData.debugMode)
10142       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10143               startedFromSetupPosition ? "position and " : "",
10144               backwardMostMove, upto, cps->which);
10145     if(currentlyInitializedVariant != gameInfo.variant) {
10146       char buf[MSG_SIZ];
10147         // [HGM] variantswitch: make engine aware of new variant
10148         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10149                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10150         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10151         SendToProgram(buf, cps);
10152         currentlyInitializedVariant = gameInfo.variant;
10153     }
10154     SendToProgram("force\n", cps);
10155     if (startedFromSetupPosition) {
10156         SendBoard(cps, backwardMostMove);
10157     if (appData.debugMode) {
10158         fprintf(debugFP, "feedMoves\n");
10159     }
10160     }
10161     for (i = backwardMostMove; i < upto; i++) {
10162         SendMoveToProgram(i, cps);
10163     }
10164 }
10165
10166
10167 int
10168 ResurrectChessProgram()
10169 {
10170      /* The chess program may have exited.
10171         If so, restart it and feed it all the moves made so far. */
10172     static int doInit = 0;
10173
10174     if (appData.noChessProgram) return 1;
10175
10176     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10177         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10178         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10179         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10180     } else {
10181         if (first.pr != NoProc) return 1;
10182         StartChessProgram(&first);
10183     }
10184     InitChessProgram(&first, FALSE);
10185     FeedMovesToProgram(&first, currentMove);
10186
10187     if (!first.sendTime) {
10188         /* can't tell gnuchess what its clock should read,
10189            so we bow to its notion. */
10190         ResetClocks();
10191         timeRemaining[0][currentMove] = whiteTimeRemaining;
10192         timeRemaining[1][currentMove] = blackTimeRemaining;
10193     }
10194
10195     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10196                 appData.icsEngineAnalyze) && first.analysisSupport) {
10197       SendToProgram("analyze\n", &first);
10198       first.analyzing = TRUE;
10199     }
10200     return 1;
10201 }
10202
10203 /*
10204  * Button procedures
10205  */
10206 void
10207 Reset(redraw, init)
10208      int redraw, init;
10209 {
10210     int i;
10211
10212     if (appData.debugMode) {
10213         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10214                 redraw, init, gameMode);
10215     }
10216     CleanupTail(); // [HGM] vari: delete any stored variations
10217     pausing = pauseExamInvalid = FALSE;
10218     startedFromSetupPosition = blackPlaysFirst = FALSE;
10219     firstMove = TRUE;
10220     whiteFlag = blackFlag = FALSE;
10221     userOfferedDraw = FALSE;
10222     hintRequested = bookRequested = FALSE;
10223     first.maybeThinking = FALSE;
10224     second.maybeThinking = FALSE;
10225     first.bookSuspend = FALSE; // [HGM] book
10226     second.bookSuspend = FALSE;
10227     thinkOutput[0] = NULLCHAR;
10228     lastHint[0] = NULLCHAR;
10229     ClearGameInfo(&gameInfo);
10230     gameInfo.variant = StringToVariant(appData.variant);
10231     ics_user_moved = ics_clock_paused = FALSE;
10232     ics_getting_history = H_FALSE;
10233     ics_gamenum = -1;
10234     white_holding[0] = black_holding[0] = NULLCHAR;
10235     ClearProgramStats();
10236     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10237
10238     ResetFrontEnd();
10239     ClearHighlights();
10240     flipView = appData.flipView;
10241     ClearPremoveHighlights();
10242     gotPremove = FALSE;
10243     alarmSounded = FALSE;
10244
10245     GameEnds(EndOfFile, NULL, GE_PLAYER);
10246     if(appData.serverMovesName != NULL) {
10247         /* [HGM] prepare to make moves file for broadcasting */
10248         clock_t t = clock();
10249         if(serverMoves != NULL) fclose(serverMoves);
10250         serverMoves = fopen(appData.serverMovesName, "r");
10251         if(serverMoves != NULL) {
10252             fclose(serverMoves);
10253             /* delay 15 sec before overwriting, so all clients can see end */
10254             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10255         }
10256         serverMoves = fopen(appData.serverMovesName, "w");
10257     }
10258
10259     ExitAnalyzeMode();
10260     gameMode = BeginningOfGame;
10261     ModeHighlight();
10262     if(appData.icsActive) gameInfo.variant = VariantNormal;
10263     currentMove = forwardMostMove = backwardMostMove = 0;
10264     InitPosition(redraw);
10265     for (i = 0; i < MAX_MOVES; i++) {
10266         if (commentList[i] != NULL) {
10267             free(commentList[i]);
10268             commentList[i] = NULL;
10269         }
10270     }
10271     ResetClocks();
10272     timeRemaining[0][0] = whiteTimeRemaining;
10273     timeRemaining[1][0] = blackTimeRemaining;
10274
10275     if (first.pr == NULL) {
10276         StartChessProgram(&first);
10277     }
10278     if (init) {
10279             InitChessProgram(&first, startedFromSetupPosition);
10280     }
10281     DisplayTitle("");
10282     DisplayMessage("", "");
10283     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10284     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10285 }
10286
10287 void
10288 AutoPlayGameLoop()
10289 {
10290     for (;;) {
10291         if (!AutoPlayOneMove())
10292           return;
10293         if (matchMode || appData.timeDelay == 0)
10294           continue;
10295         if (appData.timeDelay < 0)
10296           return;
10297         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10298         break;
10299     }
10300 }
10301
10302
10303 int
10304 AutoPlayOneMove()
10305 {
10306     int fromX, fromY, toX, toY;
10307
10308     if (appData.debugMode) {
10309       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10310     }
10311
10312     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10313       return FALSE;
10314
10315     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10316       pvInfoList[currentMove].depth = programStats.depth;
10317       pvInfoList[currentMove].score = programStats.score;
10318       pvInfoList[currentMove].time  = 0;
10319       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10320     }
10321
10322     if (currentMove >= forwardMostMove) {
10323       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10324       gameMode = EditGame;
10325       ModeHighlight();
10326
10327       /* [AS] Clear current move marker at the end of a game */
10328       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10329
10330       return FALSE;
10331     }
10332
10333     toX = moveList[currentMove][2] - AAA;
10334     toY = moveList[currentMove][3] - ONE;
10335
10336     if (moveList[currentMove][1] == '@') {
10337         if (appData.highlightLastMove) {
10338             SetHighlights(-1, -1, toX, toY);
10339         }
10340     } else {
10341         fromX = moveList[currentMove][0] - AAA;
10342         fromY = moveList[currentMove][1] - ONE;
10343
10344         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10345
10346         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10347
10348         if (appData.highlightLastMove) {
10349             SetHighlights(fromX, fromY, toX, toY);
10350         }
10351     }
10352     DisplayMove(currentMove);
10353     SendMoveToProgram(currentMove++, &first);
10354     DisplayBothClocks();
10355     DrawPosition(FALSE, boards[currentMove]);
10356     // [HGM] PV info: always display, routine tests if empty
10357     DisplayComment(currentMove - 1, commentList[currentMove]);
10358     return TRUE;
10359 }
10360
10361
10362 int
10363 LoadGameOneMove(readAhead)
10364      ChessMove readAhead;
10365 {
10366     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10367     char promoChar = NULLCHAR;
10368     ChessMove moveType;
10369     char move[MSG_SIZ];
10370     char *p, *q;
10371
10372     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10373         gameMode != AnalyzeMode && gameMode != Training) {
10374         gameFileFP = NULL;
10375         return FALSE;
10376     }
10377
10378     yyboardindex = forwardMostMove;
10379     if (readAhead != EndOfFile) {
10380       moveType = readAhead;
10381     } else {
10382       if (gameFileFP == NULL)
10383           return FALSE;
10384       moveType = (ChessMove) Myylex();
10385     }
10386
10387     done = FALSE;
10388     switch (moveType) {
10389       case Comment:
10390         if (appData.debugMode)
10391           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10392         p = yy_text;
10393
10394         /* append the comment but don't display it */
10395         AppendComment(currentMove, p, FALSE);
10396         return TRUE;
10397
10398       case WhiteCapturesEnPassant:
10399       case BlackCapturesEnPassant:
10400       case WhitePromotion:
10401       case BlackPromotion:
10402       case WhiteNonPromotion:
10403       case BlackNonPromotion:
10404       case NormalMove:
10405       case WhiteKingSideCastle:
10406       case WhiteQueenSideCastle:
10407       case BlackKingSideCastle:
10408       case BlackQueenSideCastle:
10409       case WhiteKingSideCastleWild:
10410       case WhiteQueenSideCastleWild:
10411       case BlackKingSideCastleWild:
10412       case BlackQueenSideCastleWild:
10413       /* PUSH Fabien */
10414       case WhiteHSideCastleFR:
10415       case WhiteASideCastleFR:
10416       case BlackHSideCastleFR:
10417       case BlackASideCastleFR:
10418       /* POP Fabien */
10419         if (appData.debugMode)
10420           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10421         fromX = currentMoveString[0] - AAA;
10422         fromY = currentMoveString[1] - ONE;
10423         toX = currentMoveString[2] - AAA;
10424         toY = currentMoveString[3] - ONE;
10425         promoChar = currentMoveString[4];
10426         break;
10427
10428       case WhiteDrop:
10429       case BlackDrop:
10430         if (appData.debugMode)
10431           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10432         fromX = moveType == WhiteDrop ?
10433           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10434         (int) CharToPiece(ToLower(currentMoveString[0]));
10435         fromY = DROP_RANK;
10436         toX = currentMoveString[2] - AAA;
10437         toY = currentMoveString[3] - ONE;
10438         break;
10439
10440       case WhiteWins:
10441       case BlackWins:
10442       case GameIsDrawn:
10443       case GameUnfinished:
10444         if (appData.debugMode)
10445           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10446         p = strchr(yy_text, '{');
10447         if (p == NULL) p = strchr(yy_text, '(');
10448         if (p == NULL) {
10449             p = yy_text;
10450             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10451         } else {
10452             q = strchr(p, *p == '{' ? '}' : ')');
10453             if (q != NULL) *q = NULLCHAR;
10454             p++;
10455         }
10456         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10457         GameEnds(moveType, p, GE_FILE);
10458         done = TRUE;
10459         if (cmailMsgLoaded) {
10460             ClearHighlights();
10461             flipView = WhiteOnMove(currentMove);
10462             if (moveType == GameUnfinished) flipView = !flipView;
10463             if (appData.debugMode)
10464               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10465         }
10466         break;
10467
10468       case EndOfFile:
10469         if (appData.debugMode)
10470           fprintf(debugFP, "Parser hit end of file\n");
10471         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10472           case MT_NONE:
10473           case MT_CHECK:
10474             break;
10475           case MT_CHECKMATE:
10476           case MT_STAINMATE:
10477             if (WhiteOnMove(currentMove)) {
10478                 GameEnds(BlackWins, "Black mates", GE_FILE);
10479             } else {
10480                 GameEnds(WhiteWins, "White mates", GE_FILE);
10481             }
10482             break;
10483           case MT_STALEMATE:
10484             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10485             break;
10486         }
10487         done = TRUE;
10488         break;
10489
10490       case MoveNumberOne:
10491         if (lastLoadGameStart == GNUChessGame) {
10492             /* GNUChessGames have numbers, but they aren't move numbers */
10493             if (appData.debugMode)
10494               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10495                       yy_text, (int) moveType);
10496             return LoadGameOneMove(EndOfFile); /* tail recursion */
10497         }
10498         /* else fall thru */
10499
10500       case XBoardGame:
10501       case GNUChessGame:
10502       case PGNTag:
10503         /* Reached start of next game in file */
10504         if (appData.debugMode)
10505           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10506         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10507           case MT_NONE:
10508           case MT_CHECK:
10509             break;
10510           case MT_CHECKMATE:
10511           case MT_STAINMATE:
10512             if (WhiteOnMove(currentMove)) {
10513                 GameEnds(BlackWins, "Black mates", GE_FILE);
10514             } else {
10515                 GameEnds(WhiteWins, "White mates", GE_FILE);
10516             }
10517             break;
10518           case MT_STALEMATE:
10519             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10520             break;
10521         }
10522         done = TRUE;
10523         break;
10524
10525       case PositionDiagram:     /* should not happen; ignore */
10526       case ElapsedTime:         /* ignore */
10527       case NAG:                 /* ignore */
10528         if (appData.debugMode)
10529           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10530                   yy_text, (int) moveType);
10531         return LoadGameOneMove(EndOfFile); /* tail recursion */
10532
10533       case IllegalMove:
10534         if (appData.testLegality) {
10535             if (appData.debugMode)
10536               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10537             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10538                     (forwardMostMove / 2) + 1,
10539                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10540             DisplayError(move, 0);
10541             done = TRUE;
10542         } else {
10543             if (appData.debugMode)
10544               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10545                       yy_text, currentMoveString);
10546             fromX = currentMoveString[0] - AAA;
10547             fromY = currentMoveString[1] - ONE;
10548             toX = currentMoveString[2] - AAA;
10549             toY = currentMoveString[3] - ONE;
10550             promoChar = currentMoveString[4];
10551         }
10552         break;
10553
10554       case AmbiguousMove:
10555         if (appData.debugMode)
10556           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10557         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10558                 (forwardMostMove / 2) + 1,
10559                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10560         DisplayError(move, 0);
10561         done = TRUE;
10562         break;
10563
10564       default:
10565       case ImpossibleMove:
10566         if (appData.debugMode)
10567           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10568         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10569                 (forwardMostMove / 2) + 1,
10570                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10571         DisplayError(move, 0);
10572         done = TRUE;
10573         break;
10574     }
10575
10576     if (done) {
10577         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10578             DrawPosition(FALSE, boards[currentMove]);
10579             DisplayBothClocks();
10580             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10581               DisplayComment(currentMove - 1, commentList[currentMove]);
10582         }
10583         (void) StopLoadGameTimer();
10584         gameFileFP = NULL;
10585         cmailOldMove = forwardMostMove;
10586         return FALSE;
10587     } else {
10588         /* currentMoveString is set as a side-effect of yylex */
10589
10590         thinkOutput[0] = NULLCHAR;
10591         MakeMove(fromX, fromY, toX, toY, promoChar);
10592         currentMove = forwardMostMove;
10593         return TRUE;
10594     }
10595 }
10596
10597 /* Load the nth game from the given file */
10598 int
10599 LoadGameFromFile(filename, n, title, useList)
10600      char *filename;
10601      int n;
10602      char *title;
10603      /*Boolean*/ int useList;
10604 {
10605     FILE *f;
10606     char buf[MSG_SIZ];
10607
10608     if (strcmp(filename, "-") == 0) {
10609         f = stdin;
10610         title = "stdin";
10611     } else {
10612         f = fopen(filename, "rb");
10613         if (f == NULL) {
10614           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10615             DisplayError(buf, errno);
10616             return FALSE;
10617         }
10618     }
10619     if (fseek(f, 0, 0) == -1) {
10620         /* f is not seekable; probably a pipe */
10621         useList = FALSE;
10622     }
10623     if (useList && n == 0) {
10624         int error = GameListBuild(f);
10625         if (error) {
10626             DisplayError(_("Cannot build game list"), error);
10627         } else if (!ListEmpty(&gameList) &&
10628                    ((ListGame *) gameList.tailPred)->number > 1) {
10629             GameListPopUp(f, title);
10630             return TRUE;
10631         }
10632         GameListDestroy();
10633         n = 1;
10634     }
10635     if (n == 0) n = 1;
10636     return LoadGame(f, n, title, FALSE);
10637 }
10638
10639
10640 void
10641 MakeRegisteredMove()
10642 {
10643     int fromX, fromY, toX, toY;
10644     char promoChar;
10645     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10646         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10647           case CMAIL_MOVE:
10648           case CMAIL_DRAW:
10649             if (appData.debugMode)
10650               fprintf(debugFP, "Restoring %s for game %d\n",
10651                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10652
10653             thinkOutput[0] = NULLCHAR;
10654             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10655             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10656             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10657             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10658             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10659             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10660             MakeMove(fromX, fromY, toX, toY, promoChar);
10661             ShowMove(fromX, fromY, toX, toY);
10662
10663             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10664               case MT_NONE:
10665               case MT_CHECK:
10666                 break;
10667
10668               case MT_CHECKMATE:
10669               case MT_STAINMATE:
10670                 if (WhiteOnMove(currentMove)) {
10671                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10672                 } else {
10673                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10674                 }
10675                 break;
10676
10677               case MT_STALEMATE:
10678                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10679                 break;
10680             }
10681
10682             break;
10683
10684           case CMAIL_RESIGN:
10685             if (WhiteOnMove(currentMove)) {
10686                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10687             } else {
10688                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10689             }
10690             break;
10691
10692           case CMAIL_ACCEPT:
10693             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10694             break;
10695
10696           default:
10697             break;
10698         }
10699     }
10700
10701     return;
10702 }
10703
10704 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10705 int
10706 CmailLoadGame(f, gameNumber, title, useList)
10707      FILE *f;
10708      int gameNumber;
10709      char *title;
10710      int useList;
10711 {
10712     int retVal;
10713
10714     if (gameNumber > nCmailGames) {
10715         DisplayError(_("No more games in this message"), 0);
10716         return FALSE;
10717     }
10718     if (f == lastLoadGameFP) {
10719         int offset = gameNumber - lastLoadGameNumber;
10720         if (offset == 0) {
10721             cmailMsg[0] = NULLCHAR;
10722             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10723                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10724                 nCmailMovesRegistered--;
10725             }
10726             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10727             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10728                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10729             }
10730         } else {
10731             if (! RegisterMove()) return FALSE;
10732         }
10733     }
10734
10735     retVal = LoadGame(f, gameNumber, title, useList);
10736
10737     /* Make move registered during previous look at this game, if any */
10738     MakeRegisteredMove();
10739
10740     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10741         commentList[currentMove]
10742           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10743         DisplayComment(currentMove - 1, commentList[currentMove]);
10744     }
10745
10746     return retVal;
10747 }
10748
10749 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10750 int
10751 ReloadGame(offset)
10752      int offset;
10753 {
10754     int gameNumber = lastLoadGameNumber + offset;
10755     if (lastLoadGameFP == NULL) {
10756         DisplayError(_("No game has been loaded yet"), 0);
10757         return FALSE;
10758     }
10759     if (gameNumber <= 0) {
10760         DisplayError(_("Can't back up any further"), 0);
10761         return FALSE;
10762     }
10763     if (cmailMsgLoaded) {
10764         return CmailLoadGame(lastLoadGameFP, gameNumber,
10765                              lastLoadGameTitle, lastLoadGameUseList);
10766     } else {
10767         return LoadGame(lastLoadGameFP, gameNumber,
10768                         lastLoadGameTitle, lastLoadGameUseList);
10769     }
10770 }
10771
10772
10773
10774 /* Load the nth game from open file f */
10775 int
10776 LoadGame(f, gameNumber, title, useList)
10777      FILE *f;
10778      int gameNumber;
10779      char *title;
10780      int useList;
10781 {
10782     ChessMove cm;
10783     char buf[MSG_SIZ];
10784     int gn = gameNumber;
10785     ListGame *lg = NULL;
10786     int numPGNTags = 0;
10787     int err;
10788     GameMode oldGameMode;
10789     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10790
10791     if (appData.debugMode)
10792         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10793
10794     if (gameMode == Training )
10795         SetTrainingModeOff();
10796
10797     oldGameMode = gameMode;
10798     if (gameMode != BeginningOfGame) {
10799       Reset(FALSE, TRUE);
10800     }
10801
10802     gameFileFP = f;
10803     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10804         fclose(lastLoadGameFP);
10805     }
10806
10807     if (useList) {
10808         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10809
10810         if (lg) {
10811             fseek(f, lg->offset, 0);
10812             GameListHighlight(gameNumber);
10813             gn = 1;
10814         }
10815         else {
10816             DisplayError(_("Game number out of range"), 0);
10817             return FALSE;
10818         }
10819     } else {
10820         GameListDestroy();
10821         if (fseek(f, 0, 0) == -1) {
10822             if (f == lastLoadGameFP ?
10823                 gameNumber == lastLoadGameNumber + 1 :
10824                 gameNumber == 1) {
10825                 gn = 1;
10826             } else {
10827                 DisplayError(_("Can't seek on game file"), 0);
10828                 return FALSE;
10829             }
10830         }
10831     }
10832     lastLoadGameFP = f;
10833     lastLoadGameNumber = gameNumber;
10834     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10835     lastLoadGameUseList = useList;
10836
10837     yynewfile(f);
10838
10839     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10840       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10841                 lg->gameInfo.black);
10842             DisplayTitle(buf);
10843     } else if (*title != NULLCHAR) {
10844         if (gameNumber > 1) {
10845           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10846             DisplayTitle(buf);
10847         } else {
10848             DisplayTitle(title);
10849         }
10850     }
10851
10852     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10853         gameMode = PlayFromGameFile;
10854         ModeHighlight();
10855     }
10856
10857     currentMove = forwardMostMove = backwardMostMove = 0;
10858     CopyBoard(boards[0], initialPosition);
10859     StopClocks();
10860
10861     /*
10862      * Skip the first gn-1 games in the file.
10863      * Also skip over anything that precedes an identifiable
10864      * start of game marker, to avoid being confused by
10865      * garbage at the start of the file.  Currently
10866      * recognized start of game markers are the move number "1",
10867      * the pattern "gnuchess .* game", the pattern
10868      * "^[#;%] [^ ]* game file", and a PGN tag block.
10869      * A game that starts with one of the latter two patterns
10870      * will also have a move number 1, possibly
10871      * following a position diagram.
10872      * 5-4-02: Let's try being more lenient and allowing a game to
10873      * start with an unnumbered move.  Does that break anything?
10874      */
10875     cm = lastLoadGameStart = EndOfFile;
10876     while (gn > 0) {
10877         yyboardindex = forwardMostMove;
10878         cm = (ChessMove) Myylex();
10879         switch (cm) {
10880           case EndOfFile:
10881             if (cmailMsgLoaded) {
10882                 nCmailGames = CMAIL_MAX_GAMES - gn;
10883             } else {
10884                 Reset(TRUE, TRUE);
10885                 DisplayError(_("Game not found in file"), 0);
10886             }
10887             return FALSE;
10888
10889           case GNUChessGame:
10890           case XBoardGame:
10891             gn--;
10892             lastLoadGameStart = cm;
10893             break;
10894
10895           case MoveNumberOne:
10896             switch (lastLoadGameStart) {
10897               case GNUChessGame:
10898               case XBoardGame:
10899               case PGNTag:
10900                 break;
10901               case MoveNumberOne:
10902               case EndOfFile:
10903                 gn--;           /* count this game */
10904                 lastLoadGameStart = cm;
10905                 break;
10906               default:
10907                 /* impossible */
10908                 break;
10909             }
10910             break;
10911
10912           case PGNTag:
10913             switch (lastLoadGameStart) {
10914               case GNUChessGame:
10915               case PGNTag:
10916               case MoveNumberOne:
10917               case EndOfFile:
10918                 gn--;           /* count this game */
10919                 lastLoadGameStart = cm;
10920                 break;
10921               case XBoardGame:
10922                 lastLoadGameStart = cm; /* game counted already */
10923                 break;
10924               default:
10925                 /* impossible */
10926                 break;
10927             }
10928             if (gn > 0) {
10929                 do {
10930                     yyboardindex = forwardMostMove;
10931                     cm = (ChessMove) Myylex();
10932                 } while (cm == PGNTag || cm == Comment);
10933             }
10934             break;
10935
10936           case WhiteWins:
10937           case BlackWins:
10938           case GameIsDrawn:
10939             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10940                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10941                     != CMAIL_OLD_RESULT) {
10942                     nCmailResults ++ ;
10943                     cmailResult[  CMAIL_MAX_GAMES
10944                                 - gn - 1] = CMAIL_OLD_RESULT;
10945                 }
10946             }
10947             break;
10948
10949           case NormalMove:
10950             /* Only a NormalMove can be at the start of a game
10951              * without a position diagram. */
10952             if (lastLoadGameStart == EndOfFile ) {
10953               gn--;
10954               lastLoadGameStart = MoveNumberOne;
10955             }
10956             break;
10957
10958           default:
10959             break;
10960         }
10961     }
10962
10963     if (appData.debugMode)
10964       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10965
10966     if (cm == XBoardGame) {
10967         /* Skip any header junk before position diagram and/or move 1 */
10968         for (;;) {
10969             yyboardindex = forwardMostMove;
10970             cm = (ChessMove) Myylex();
10971
10972             if (cm == EndOfFile ||
10973                 cm == GNUChessGame || cm == XBoardGame) {
10974                 /* Empty game; pretend end-of-file and handle later */
10975                 cm = EndOfFile;
10976                 break;
10977             }
10978
10979             if (cm == MoveNumberOne || cm == PositionDiagram ||
10980                 cm == PGNTag || cm == Comment)
10981               break;
10982         }
10983     } else if (cm == GNUChessGame) {
10984         if (gameInfo.event != NULL) {
10985             free(gameInfo.event);
10986         }
10987         gameInfo.event = StrSave(yy_text);
10988     }
10989
10990     startedFromSetupPosition = FALSE;
10991     while (cm == PGNTag) {
10992         if (appData.debugMode)
10993           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10994         err = ParsePGNTag(yy_text, &gameInfo);
10995         if (!err) numPGNTags++;
10996
10997         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10998         if(gameInfo.variant != oldVariant) {
10999             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11000             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11001             InitPosition(TRUE);
11002             oldVariant = gameInfo.variant;
11003             if (appData.debugMode)
11004               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11005         }
11006
11007
11008         if (gameInfo.fen != NULL) {
11009           Board initial_position;
11010           startedFromSetupPosition = TRUE;
11011           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11012             Reset(TRUE, TRUE);
11013             DisplayError(_("Bad FEN position in file"), 0);
11014             return FALSE;
11015           }
11016           CopyBoard(boards[0], initial_position);
11017           if (blackPlaysFirst) {
11018             currentMove = forwardMostMove = backwardMostMove = 1;
11019             CopyBoard(boards[1], initial_position);
11020             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11021             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11022             timeRemaining[0][1] = whiteTimeRemaining;
11023             timeRemaining[1][1] = blackTimeRemaining;
11024             if (commentList[0] != NULL) {
11025               commentList[1] = commentList[0];
11026               commentList[0] = NULL;
11027             }
11028           } else {
11029             currentMove = forwardMostMove = backwardMostMove = 0;
11030           }
11031           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11032           {   int i;
11033               initialRulePlies = FENrulePlies;
11034               for( i=0; i< nrCastlingRights; i++ )
11035                   initialRights[i] = initial_position[CASTLING][i];
11036           }
11037           yyboardindex = forwardMostMove;
11038           free(gameInfo.fen);
11039           gameInfo.fen = NULL;
11040         }
11041
11042         yyboardindex = forwardMostMove;
11043         cm = (ChessMove) Myylex();
11044
11045         /* Handle comments interspersed among the tags */
11046         while (cm == Comment) {
11047             char *p;
11048             if (appData.debugMode)
11049               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11050             p = yy_text;
11051             AppendComment(currentMove, p, FALSE);
11052             yyboardindex = forwardMostMove;
11053             cm = (ChessMove) Myylex();
11054         }
11055     }
11056
11057     /* don't rely on existence of Event tag since if game was
11058      * pasted from clipboard the Event tag may not exist
11059      */
11060     if (numPGNTags > 0){
11061         char *tags;
11062         if (gameInfo.variant == VariantNormal) {
11063           VariantClass v = StringToVariant(gameInfo.event);
11064           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11065           if(v < VariantShogi) gameInfo.variant = v;
11066         }
11067         if (!matchMode) {
11068           if( appData.autoDisplayTags ) {
11069             tags = PGNTags(&gameInfo);
11070             TagsPopUp(tags, CmailMsg());
11071             free(tags);
11072           }
11073         }
11074     } else {
11075         /* Make something up, but don't display it now */
11076         SetGameInfo();
11077         TagsPopDown();
11078     }
11079
11080     if (cm == PositionDiagram) {
11081         int i, j;
11082         char *p;
11083         Board initial_position;
11084
11085         if (appData.debugMode)
11086           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11087
11088         if (!startedFromSetupPosition) {
11089             p = yy_text;
11090             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11091               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11092                 switch (*p) {
11093                   case '{':
11094                   case '[':
11095                   case '-':
11096                   case ' ':
11097                   case '\t':
11098                   case '\n':
11099                   case '\r':
11100                     break;
11101                   default:
11102                     initial_position[i][j++] = CharToPiece(*p);
11103                     break;
11104                 }
11105             while (*p == ' ' || *p == '\t' ||
11106                    *p == '\n' || *p == '\r') p++;
11107
11108             if (strncmp(p, "black", strlen("black"))==0)
11109               blackPlaysFirst = TRUE;
11110             else
11111               blackPlaysFirst = FALSE;
11112             startedFromSetupPosition = TRUE;
11113
11114             CopyBoard(boards[0], initial_position);
11115             if (blackPlaysFirst) {
11116                 currentMove = forwardMostMove = backwardMostMove = 1;
11117                 CopyBoard(boards[1], initial_position);
11118                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11119                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11120                 timeRemaining[0][1] = whiteTimeRemaining;
11121                 timeRemaining[1][1] = blackTimeRemaining;
11122                 if (commentList[0] != NULL) {
11123                     commentList[1] = commentList[0];
11124                     commentList[0] = NULL;
11125                 }
11126             } else {
11127                 currentMove = forwardMostMove = backwardMostMove = 0;
11128             }
11129         }
11130         yyboardindex = forwardMostMove;
11131         cm = (ChessMove) Myylex();
11132     }
11133
11134     if (first.pr == NoProc) {
11135         StartChessProgram(&first);
11136     }
11137     InitChessProgram(&first, FALSE);
11138     SendToProgram("force\n", &first);
11139     if (startedFromSetupPosition) {
11140         SendBoard(&first, forwardMostMove);
11141     if (appData.debugMode) {
11142         fprintf(debugFP, "Load Game\n");
11143     }
11144         DisplayBothClocks();
11145     }
11146
11147     /* [HGM] server: flag to write setup moves in broadcast file as one */
11148     loadFlag = appData.suppressLoadMoves;
11149
11150     while (cm == Comment) {
11151         char *p;
11152         if (appData.debugMode)
11153           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11154         p = yy_text;
11155         AppendComment(currentMove, p, FALSE);
11156         yyboardindex = forwardMostMove;
11157         cm = (ChessMove) Myylex();
11158     }
11159
11160     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11161         cm == WhiteWins || cm == BlackWins ||
11162         cm == GameIsDrawn || cm == GameUnfinished) {
11163         DisplayMessage("", _("No moves in game"));
11164         if (cmailMsgLoaded) {
11165             if (appData.debugMode)
11166               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11167             ClearHighlights();
11168             flipView = FALSE;
11169         }
11170         DrawPosition(FALSE, boards[currentMove]);
11171         DisplayBothClocks();
11172         gameMode = EditGame;
11173         ModeHighlight();
11174         gameFileFP = NULL;
11175         cmailOldMove = 0;
11176         return TRUE;
11177     }
11178
11179     // [HGM] PV info: routine tests if comment empty
11180     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11181         DisplayComment(currentMove - 1, commentList[currentMove]);
11182     }
11183     if (!matchMode && appData.timeDelay != 0)
11184       DrawPosition(FALSE, boards[currentMove]);
11185
11186     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11187       programStats.ok_to_send = 1;
11188     }
11189
11190     /* if the first token after the PGN tags is a move
11191      * and not move number 1, retrieve it from the parser
11192      */
11193     if (cm != MoveNumberOne)
11194         LoadGameOneMove(cm);
11195
11196     /* load the remaining moves from the file */
11197     while (LoadGameOneMove(EndOfFile)) {
11198       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11199       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11200     }
11201
11202     /* rewind to the start of the game */
11203     currentMove = backwardMostMove;
11204
11205     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11206
11207     if (oldGameMode == AnalyzeFile ||
11208         oldGameMode == AnalyzeMode) {
11209       AnalyzeFileEvent();
11210     }
11211
11212     if (matchMode || appData.timeDelay == 0) {
11213       ToEndEvent();
11214       gameMode = EditGame;
11215       ModeHighlight();
11216     } else if (appData.timeDelay > 0) {
11217       AutoPlayGameLoop();
11218     }
11219
11220     if (appData.debugMode)
11221         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11222
11223     loadFlag = 0; /* [HGM] true game starts */
11224     return TRUE;
11225 }
11226
11227 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11228 int
11229 ReloadPosition(offset)
11230      int offset;
11231 {
11232     int positionNumber = lastLoadPositionNumber + offset;
11233     if (lastLoadPositionFP == NULL) {
11234         DisplayError(_("No position has been loaded yet"), 0);
11235         return FALSE;
11236     }
11237     if (positionNumber <= 0) {
11238         DisplayError(_("Can't back up any further"), 0);
11239         return FALSE;
11240     }
11241     return LoadPosition(lastLoadPositionFP, positionNumber,
11242                         lastLoadPositionTitle);
11243 }
11244
11245 /* Load the nth position from the given file */
11246 int
11247 LoadPositionFromFile(filename, n, title)
11248      char *filename;
11249      int n;
11250      char *title;
11251 {
11252     FILE *f;
11253     char buf[MSG_SIZ];
11254
11255     if (strcmp(filename, "-") == 0) {
11256         return LoadPosition(stdin, n, "stdin");
11257     } else {
11258         f = fopen(filename, "rb");
11259         if (f == NULL) {
11260             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11261             DisplayError(buf, errno);
11262             return FALSE;
11263         } else {
11264             return LoadPosition(f, n, title);
11265         }
11266     }
11267 }
11268
11269 /* Load the nth position from the given open file, and close it */
11270 int
11271 LoadPosition(f, positionNumber, title)
11272      FILE *f;
11273      int positionNumber;
11274      char *title;
11275 {
11276     char *p, line[MSG_SIZ];
11277     Board initial_position;
11278     int i, j, fenMode, pn;
11279
11280     if (gameMode == Training )
11281         SetTrainingModeOff();
11282
11283     if (gameMode != BeginningOfGame) {
11284         Reset(FALSE, TRUE);
11285     }
11286     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11287         fclose(lastLoadPositionFP);
11288     }
11289     if (positionNumber == 0) positionNumber = 1;
11290     lastLoadPositionFP = f;
11291     lastLoadPositionNumber = positionNumber;
11292     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11293     if (first.pr == NoProc) {
11294       StartChessProgram(&first);
11295       InitChessProgram(&first, FALSE);
11296     }
11297     pn = positionNumber;
11298     if (positionNumber < 0) {
11299         /* Negative position number means to seek to that byte offset */
11300         if (fseek(f, -positionNumber, 0) == -1) {
11301             DisplayError(_("Can't seek on position file"), 0);
11302             return FALSE;
11303         };
11304         pn = 1;
11305     } else {
11306         if (fseek(f, 0, 0) == -1) {
11307             if (f == lastLoadPositionFP ?
11308                 positionNumber == lastLoadPositionNumber + 1 :
11309                 positionNumber == 1) {
11310                 pn = 1;
11311             } else {
11312                 DisplayError(_("Can't seek on position file"), 0);
11313                 return FALSE;
11314             }
11315         }
11316     }
11317     /* See if this file is FEN or old-style xboard */
11318     if (fgets(line, MSG_SIZ, f) == NULL) {
11319         DisplayError(_("Position not found in file"), 0);
11320         return FALSE;
11321     }
11322     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11323     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11324
11325     if (pn >= 2) {
11326         if (fenMode || line[0] == '#') pn--;
11327         while (pn > 0) {
11328             /* skip positions before number pn */
11329             if (fgets(line, MSG_SIZ, f) == NULL) {
11330                 Reset(TRUE, TRUE);
11331                 DisplayError(_("Position not found in file"), 0);
11332                 return FALSE;
11333             }
11334             if (fenMode || line[0] == '#') pn--;
11335         }
11336     }
11337
11338     if (fenMode) {
11339         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11340             DisplayError(_("Bad FEN position in file"), 0);
11341             return FALSE;
11342         }
11343     } else {
11344         (void) fgets(line, MSG_SIZ, f);
11345         (void) fgets(line, MSG_SIZ, f);
11346
11347         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11348             (void) fgets(line, MSG_SIZ, f);
11349             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11350                 if (*p == ' ')
11351                   continue;
11352                 initial_position[i][j++] = CharToPiece(*p);
11353             }
11354         }
11355
11356         blackPlaysFirst = FALSE;
11357         if (!feof(f)) {
11358             (void) fgets(line, MSG_SIZ, f);
11359             if (strncmp(line, "black", strlen("black"))==0)
11360               blackPlaysFirst = TRUE;
11361         }
11362     }
11363     startedFromSetupPosition = TRUE;
11364
11365     SendToProgram("force\n", &first);
11366     CopyBoard(boards[0], initial_position);
11367     if (blackPlaysFirst) {
11368         currentMove = forwardMostMove = backwardMostMove = 1;
11369         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11370         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11371         CopyBoard(boards[1], initial_position);
11372         DisplayMessage("", _("Black to play"));
11373     } else {
11374         currentMove = forwardMostMove = backwardMostMove = 0;
11375         DisplayMessage("", _("White to play"));
11376     }
11377     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11378     SendBoard(&first, forwardMostMove);
11379     if (appData.debugMode) {
11380 int i, j;
11381   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11382   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11383         fprintf(debugFP, "Load Position\n");
11384     }
11385
11386     if (positionNumber > 1) {
11387       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11388         DisplayTitle(line);
11389     } else {
11390         DisplayTitle(title);
11391     }
11392     gameMode = EditGame;
11393     ModeHighlight();
11394     ResetClocks();
11395     timeRemaining[0][1] = whiteTimeRemaining;
11396     timeRemaining[1][1] = blackTimeRemaining;
11397     DrawPosition(FALSE, boards[currentMove]);
11398
11399     return TRUE;
11400 }
11401
11402
11403 void
11404 CopyPlayerNameIntoFileName(dest, src)
11405      char **dest, *src;
11406 {
11407     while (*src != NULLCHAR && *src != ',') {
11408         if (*src == ' ') {
11409             *(*dest)++ = '_';
11410             src++;
11411         } else {
11412             *(*dest)++ = *src++;
11413         }
11414     }
11415 }
11416
11417 char *DefaultFileName(ext)
11418      char *ext;
11419 {
11420     static char def[MSG_SIZ];
11421     char *p;
11422
11423     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11424         p = def;
11425         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11426         *p++ = '-';
11427         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11428         *p++ = '.';
11429         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11430     } else {
11431         def[0] = NULLCHAR;
11432     }
11433     return def;
11434 }
11435
11436 /* Save the current game to the given file */
11437 int
11438 SaveGameToFile(filename, append)
11439      char *filename;
11440      int append;
11441 {
11442     FILE *f;
11443     char buf[MSG_SIZ];
11444     int result;
11445
11446     if (strcmp(filename, "-") == 0) {
11447         return SaveGame(stdout, 0, NULL);
11448     } else {
11449         f = fopen(filename, append ? "a" : "w");
11450         if (f == NULL) {
11451             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11452             DisplayError(buf, errno);
11453             return FALSE;
11454         } else {
11455             safeStrCpy(buf, lastMsg, MSG_SIZ);
11456             DisplayMessage(_("Waiting for access to save file"), "");
11457             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11458             DisplayMessage(_("Saving game"), "");
11459             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11460             result = SaveGame(f, 0, NULL);
11461             DisplayMessage(buf, "");
11462             return result;
11463         }
11464     }
11465 }
11466
11467 char *
11468 SavePart(str)
11469      char *str;
11470 {
11471     static char buf[MSG_SIZ];
11472     char *p;
11473
11474     p = strchr(str, ' ');
11475     if (p == NULL) return str;
11476     strncpy(buf, str, p - str);
11477     buf[p - str] = NULLCHAR;
11478     return buf;
11479 }
11480
11481 #define PGN_MAX_LINE 75
11482
11483 #define PGN_SIDE_WHITE  0
11484 #define PGN_SIDE_BLACK  1
11485
11486 /* [AS] */
11487 static int FindFirstMoveOutOfBook( int side )
11488 {
11489     int result = -1;
11490
11491     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11492         int index = backwardMostMove;
11493         int has_book_hit = 0;
11494
11495         if( (index % 2) != side ) {
11496             index++;
11497         }
11498
11499         while( index < forwardMostMove ) {
11500             /* Check to see if engine is in book */
11501             int depth = pvInfoList[index].depth;
11502             int score = pvInfoList[index].score;
11503             int in_book = 0;
11504
11505             if( depth <= 2 ) {
11506                 in_book = 1;
11507             }
11508             else if( score == 0 && depth == 63 ) {
11509                 in_book = 1; /* Zappa */
11510             }
11511             else if( score == 2 && depth == 99 ) {
11512                 in_book = 1; /* Abrok */
11513             }
11514
11515             has_book_hit += in_book;
11516
11517             if( ! in_book ) {
11518                 result = index;
11519
11520                 break;
11521             }
11522
11523             index += 2;
11524         }
11525     }
11526
11527     return result;
11528 }
11529
11530 /* [AS] */
11531 void GetOutOfBookInfo( char * buf )
11532 {
11533     int oob[2];
11534     int i;
11535     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11536
11537     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11538     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11539
11540     *buf = '\0';
11541
11542     if( oob[0] >= 0 || oob[1] >= 0 ) {
11543         for( i=0; i<2; i++ ) {
11544             int idx = oob[i];
11545
11546             if( idx >= 0 ) {
11547                 if( i > 0 && oob[0] >= 0 ) {
11548                     strcat( buf, "   " );
11549                 }
11550
11551                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11552                 sprintf( buf+strlen(buf), "%s%.2f",
11553                     pvInfoList[idx].score >= 0 ? "+" : "",
11554                     pvInfoList[idx].score / 100.0 );
11555             }
11556         }
11557     }
11558 }
11559
11560 /* Save game in PGN style and close the file */
11561 int
11562 SaveGamePGN(f)
11563      FILE *f;
11564 {
11565     int i, offset, linelen, newblock;
11566     time_t tm;
11567 //    char *movetext;
11568     char numtext[32];
11569     int movelen, numlen, blank;
11570     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11571
11572     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11573
11574     tm = time((time_t *) NULL);
11575
11576     PrintPGNTags(f, &gameInfo);
11577
11578     if (backwardMostMove > 0 || startedFromSetupPosition) {
11579         char *fen = PositionToFEN(backwardMostMove, NULL);
11580         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11581         fprintf(f, "\n{--------------\n");
11582         PrintPosition(f, backwardMostMove);
11583         fprintf(f, "--------------}\n");
11584         free(fen);
11585     }
11586     else {
11587         /* [AS] Out of book annotation */
11588         if( appData.saveOutOfBookInfo ) {
11589             char buf[64];
11590
11591             GetOutOfBookInfo( buf );
11592
11593             if( buf[0] != '\0' ) {
11594                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11595             }
11596         }
11597
11598         fprintf(f, "\n");
11599     }
11600
11601     i = backwardMostMove;
11602     linelen = 0;
11603     newblock = TRUE;
11604
11605     while (i < forwardMostMove) {
11606         /* Print comments preceding this move */
11607         if (commentList[i] != NULL) {
11608             if (linelen > 0) fprintf(f, "\n");
11609             fprintf(f, "%s", commentList[i]);
11610             linelen = 0;
11611             newblock = TRUE;
11612         }
11613
11614         /* Format move number */
11615         if ((i % 2) == 0)
11616           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11617         else
11618           if (newblock)
11619             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11620           else
11621             numtext[0] = NULLCHAR;
11622
11623         numlen = strlen(numtext);
11624         newblock = FALSE;
11625
11626         /* Print move number */
11627         blank = linelen > 0 && numlen > 0;
11628         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11629             fprintf(f, "\n");
11630             linelen = 0;
11631             blank = 0;
11632         }
11633         if (blank) {
11634             fprintf(f, " ");
11635             linelen++;
11636         }
11637         fprintf(f, "%s", numtext);
11638         linelen += numlen;
11639
11640         /* Get move */
11641         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11642         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11643
11644         /* Print move */
11645         blank = linelen > 0 && movelen > 0;
11646         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11647             fprintf(f, "\n");
11648             linelen = 0;
11649             blank = 0;
11650         }
11651         if (blank) {
11652             fprintf(f, " ");
11653             linelen++;
11654         }
11655         fprintf(f, "%s", move_buffer);
11656         linelen += movelen;
11657
11658         /* [AS] Add PV info if present */
11659         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11660             /* [HGM] add time */
11661             char buf[MSG_SIZ]; int seconds;
11662
11663             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11664
11665             if( seconds <= 0)
11666               buf[0] = 0;
11667             else
11668               if( seconds < 30 )
11669                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11670               else
11671                 {
11672                   seconds = (seconds + 4)/10; // round to full seconds
11673                   if( seconds < 60 )
11674                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11675                   else
11676                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11677                 }
11678
11679             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11680                       pvInfoList[i].score >= 0 ? "+" : "",
11681                       pvInfoList[i].score / 100.0,
11682                       pvInfoList[i].depth,
11683                       buf );
11684
11685             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11686
11687             /* Print score/depth */
11688             blank = linelen > 0 && movelen > 0;
11689             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11690                 fprintf(f, "\n");
11691                 linelen = 0;
11692                 blank = 0;
11693             }
11694             if (blank) {
11695                 fprintf(f, " ");
11696                 linelen++;
11697             }
11698             fprintf(f, "%s", move_buffer);
11699             linelen += movelen;
11700         }
11701
11702         i++;
11703     }
11704
11705     /* Start a new line */
11706     if (linelen > 0) fprintf(f, "\n");
11707
11708     /* Print comments after last move */
11709     if (commentList[i] != NULL) {
11710         fprintf(f, "%s\n", commentList[i]);
11711     }
11712
11713     /* Print result */
11714     if (gameInfo.resultDetails != NULL &&
11715         gameInfo.resultDetails[0] != NULLCHAR) {
11716         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11717                 PGNResult(gameInfo.result));
11718     } else {
11719         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11720     }
11721
11722     fclose(f);
11723     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11724     return TRUE;
11725 }
11726
11727 /* Save game in old style and close the file */
11728 int
11729 SaveGameOldStyle(f)
11730      FILE *f;
11731 {
11732     int i, offset;
11733     time_t tm;
11734
11735     tm = time((time_t *) NULL);
11736
11737     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11738     PrintOpponents(f);
11739
11740     if (backwardMostMove > 0 || startedFromSetupPosition) {
11741         fprintf(f, "\n[--------------\n");
11742         PrintPosition(f, backwardMostMove);
11743         fprintf(f, "--------------]\n");
11744     } else {
11745         fprintf(f, "\n");
11746     }
11747
11748     i = backwardMostMove;
11749     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11750
11751     while (i < forwardMostMove) {
11752         if (commentList[i] != NULL) {
11753             fprintf(f, "[%s]\n", commentList[i]);
11754         }
11755
11756         if ((i % 2) == 1) {
11757             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11758             i++;
11759         } else {
11760             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11761             i++;
11762             if (commentList[i] != NULL) {
11763                 fprintf(f, "\n");
11764                 continue;
11765             }
11766             if (i >= forwardMostMove) {
11767                 fprintf(f, "\n");
11768                 break;
11769             }
11770             fprintf(f, "%s\n", parseList[i]);
11771             i++;
11772         }
11773     }
11774
11775     if (commentList[i] != NULL) {
11776         fprintf(f, "[%s]\n", commentList[i]);
11777     }
11778
11779     /* This isn't really the old style, but it's close enough */
11780     if (gameInfo.resultDetails != NULL &&
11781         gameInfo.resultDetails[0] != NULLCHAR) {
11782         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11783                 gameInfo.resultDetails);
11784     } else {
11785         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11786     }
11787
11788     fclose(f);
11789     return TRUE;
11790 }
11791
11792 /* Save the current game to open file f and close the file */
11793 int
11794 SaveGame(f, dummy, dummy2)
11795      FILE *f;
11796      int dummy;
11797      char *dummy2;
11798 {
11799     if (gameMode == EditPosition) EditPositionDone(TRUE);
11800     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11801     if (appData.oldSaveStyle)
11802       return SaveGameOldStyle(f);
11803     else
11804       return SaveGamePGN(f);
11805 }
11806
11807 /* Save the current position to the given file */
11808 int
11809 SavePositionToFile(filename)
11810      char *filename;
11811 {
11812     FILE *f;
11813     char buf[MSG_SIZ];
11814
11815     if (strcmp(filename, "-") == 0) {
11816         return SavePosition(stdout, 0, NULL);
11817     } else {
11818         f = fopen(filename, "a");
11819         if (f == NULL) {
11820             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11821             DisplayError(buf, errno);
11822             return FALSE;
11823         } else {
11824             safeStrCpy(buf, lastMsg, MSG_SIZ);
11825             DisplayMessage(_("Waiting for access to save file"), "");
11826             flock(fileno(f), LOCK_EX); // [HGM] lock
11827             DisplayMessage(_("Saving position"), "");
11828             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11829             SavePosition(f, 0, NULL);
11830             DisplayMessage(buf, "");
11831             return TRUE;
11832         }
11833     }
11834 }
11835
11836 /* Save the current position to the given open file and close the file */
11837 int
11838 SavePosition(f, dummy, dummy2)
11839      FILE *f;
11840      int dummy;
11841      char *dummy2;
11842 {
11843     time_t tm;
11844     char *fen;
11845
11846     if (gameMode == EditPosition) EditPositionDone(TRUE);
11847     if (appData.oldSaveStyle) {
11848         tm = time((time_t *) NULL);
11849
11850         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11851         PrintOpponents(f);
11852         fprintf(f, "[--------------\n");
11853         PrintPosition(f, currentMove);
11854         fprintf(f, "--------------]\n");
11855     } else {
11856         fen = PositionToFEN(currentMove, NULL);
11857         fprintf(f, "%s\n", fen);
11858         free(fen);
11859     }
11860     fclose(f);
11861     return TRUE;
11862 }
11863
11864 void
11865 ReloadCmailMsgEvent(unregister)
11866      int unregister;
11867 {
11868 #if !WIN32
11869     static char *inFilename = NULL;
11870     static char *outFilename;
11871     int i;
11872     struct stat inbuf, outbuf;
11873     int status;
11874
11875     /* Any registered moves are unregistered if unregister is set, */
11876     /* i.e. invoked by the signal handler */
11877     if (unregister) {
11878         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11879             cmailMoveRegistered[i] = FALSE;
11880             if (cmailCommentList[i] != NULL) {
11881                 free(cmailCommentList[i]);
11882                 cmailCommentList[i] = NULL;
11883             }
11884         }
11885         nCmailMovesRegistered = 0;
11886     }
11887
11888     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11889         cmailResult[i] = CMAIL_NOT_RESULT;
11890     }
11891     nCmailResults = 0;
11892
11893     if (inFilename == NULL) {
11894         /* Because the filenames are static they only get malloced once  */
11895         /* and they never get freed                                      */
11896         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11897         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11898
11899         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11900         sprintf(outFilename, "%s.out", appData.cmailGameName);
11901     }
11902
11903     status = stat(outFilename, &outbuf);
11904     if (status < 0) {
11905         cmailMailedMove = FALSE;
11906     } else {
11907         status = stat(inFilename, &inbuf);
11908         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11909     }
11910
11911     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11912        counts the games, notes how each one terminated, etc.
11913
11914        It would be nice to remove this kludge and instead gather all
11915        the information while building the game list.  (And to keep it
11916        in the game list nodes instead of having a bunch of fixed-size
11917        parallel arrays.)  Note this will require getting each game's
11918        termination from the PGN tags, as the game list builder does
11919        not process the game moves.  --mann
11920        */
11921     cmailMsgLoaded = TRUE;
11922     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11923
11924     /* Load first game in the file or popup game menu */
11925     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11926
11927 #endif /* !WIN32 */
11928     return;
11929 }
11930
11931 int
11932 RegisterMove()
11933 {
11934     FILE *f;
11935     char string[MSG_SIZ];
11936
11937     if (   cmailMailedMove
11938         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11939         return TRUE;            /* Allow free viewing  */
11940     }
11941
11942     /* Unregister move to ensure that we don't leave RegisterMove        */
11943     /* with the move registered when the conditions for registering no   */
11944     /* longer hold                                                       */
11945     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11946         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11947         nCmailMovesRegistered --;
11948
11949         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11950           {
11951               free(cmailCommentList[lastLoadGameNumber - 1]);
11952               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11953           }
11954     }
11955
11956     if (cmailOldMove == -1) {
11957         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11958         return FALSE;
11959     }
11960
11961     if (currentMove > cmailOldMove + 1) {
11962         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11963         return FALSE;
11964     }
11965
11966     if (currentMove < cmailOldMove) {
11967         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11968         return FALSE;
11969     }
11970
11971     if (forwardMostMove > currentMove) {
11972         /* Silently truncate extra moves */
11973         TruncateGame();
11974     }
11975
11976     if (   (currentMove == cmailOldMove + 1)
11977         || (   (currentMove == cmailOldMove)
11978             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11979                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11980         if (gameInfo.result != GameUnfinished) {
11981             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11982         }
11983
11984         if (commentList[currentMove] != NULL) {
11985             cmailCommentList[lastLoadGameNumber - 1]
11986               = StrSave(commentList[currentMove]);
11987         }
11988         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11989
11990         if (appData.debugMode)
11991           fprintf(debugFP, "Saving %s for game %d\n",
11992                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11993
11994         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11995
11996         f = fopen(string, "w");
11997         if (appData.oldSaveStyle) {
11998             SaveGameOldStyle(f); /* also closes the file */
11999
12000             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12001             f = fopen(string, "w");
12002             SavePosition(f, 0, NULL); /* also closes the file */
12003         } else {
12004             fprintf(f, "{--------------\n");
12005             PrintPosition(f, currentMove);
12006             fprintf(f, "--------------}\n\n");
12007
12008             SaveGame(f, 0, NULL); /* also closes the file*/
12009         }
12010
12011         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12012         nCmailMovesRegistered ++;
12013     } else if (nCmailGames == 1) {
12014         DisplayError(_("You have not made a move yet"), 0);
12015         return FALSE;
12016     }
12017
12018     return TRUE;
12019 }
12020
12021 void
12022 MailMoveEvent()
12023 {
12024 #if !WIN32
12025     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12026     FILE *commandOutput;
12027     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12028     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12029     int nBuffers;
12030     int i;
12031     int archived;
12032     char *arcDir;
12033
12034     if (! cmailMsgLoaded) {
12035         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12036         return;
12037     }
12038
12039     if (nCmailGames == nCmailResults) {
12040         DisplayError(_("No unfinished games"), 0);
12041         return;
12042     }
12043
12044 #if CMAIL_PROHIBIT_REMAIL
12045     if (cmailMailedMove) {
12046       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);
12047         DisplayError(msg, 0);
12048         return;
12049     }
12050 #endif
12051
12052     if (! (cmailMailedMove || RegisterMove())) return;
12053
12054     if (   cmailMailedMove
12055         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12056       snprintf(string, MSG_SIZ, partCommandString,
12057                appData.debugMode ? " -v" : "", appData.cmailGameName);
12058         commandOutput = popen(string, "r");
12059
12060         if (commandOutput == NULL) {
12061             DisplayError(_("Failed to invoke cmail"), 0);
12062         } else {
12063             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12064                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12065             }
12066             if (nBuffers > 1) {
12067                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12068                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12069                 nBytes = MSG_SIZ - 1;
12070             } else {
12071                 (void) memcpy(msg, buffer, nBytes);
12072             }
12073             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12074
12075             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12076                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12077
12078                 archived = TRUE;
12079                 for (i = 0; i < nCmailGames; i ++) {
12080                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12081                         archived = FALSE;
12082                     }
12083                 }
12084                 if (   archived
12085                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12086                         != NULL)) {
12087                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12088                            arcDir,
12089                            appData.cmailGameName,
12090                            gameInfo.date);
12091                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12092                     cmailMsgLoaded = FALSE;
12093                 }
12094             }
12095
12096             DisplayInformation(msg);
12097             pclose(commandOutput);
12098         }
12099     } else {
12100         if ((*cmailMsg) != '\0') {
12101             DisplayInformation(cmailMsg);
12102         }
12103     }
12104
12105     return;
12106 #endif /* !WIN32 */
12107 }
12108
12109 char *
12110 CmailMsg()
12111 {
12112 #if WIN32
12113     return NULL;
12114 #else
12115     int  prependComma = 0;
12116     char number[5];
12117     char string[MSG_SIZ];       /* Space for game-list */
12118     int  i;
12119
12120     if (!cmailMsgLoaded) return "";
12121
12122     if (cmailMailedMove) {
12123       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12124     } else {
12125         /* Create a list of games left */
12126       snprintf(string, MSG_SIZ, "[");
12127         for (i = 0; i < nCmailGames; i ++) {
12128             if (! (   cmailMoveRegistered[i]
12129                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12130                 if (prependComma) {
12131                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12132                 } else {
12133                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12134                     prependComma = 1;
12135                 }
12136
12137                 strcat(string, number);
12138             }
12139         }
12140         strcat(string, "]");
12141
12142         if (nCmailMovesRegistered + nCmailResults == 0) {
12143             switch (nCmailGames) {
12144               case 1:
12145                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12146                 break;
12147
12148               case 2:
12149                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12150                 break;
12151
12152               default:
12153                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12154                          nCmailGames);
12155                 break;
12156             }
12157         } else {
12158             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12159               case 1:
12160                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12161                          string);
12162                 break;
12163
12164               case 0:
12165                 if (nCmailResults == nCmailGames) {
12166                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12167                 } else {
12168                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12169                 }
12170                 break;
12171
12172               default:
12173                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12174                          string);
12175             }
12176         }
12177     }
12178     return cmailMsg;
12179 #endif /* WIN32 */
12180 }
12181
12182 void
12183 ResetGameEvent()
12184 {
12185     if (gameMode == Training)
12186       SetTrainingModeOff();
12187
12188     Reset(TRUE, TRUE);
12189     cmailMsgLoaded = FALSE;
12190     if (appData.icsActive) {
12191       SendToICS(ics_prefix);
12192       SendToICS("refresh\n");
12193     }
12194 }
12195
12196 void
12197 ExitEvent(status)
12198      int status;
12199 {
12200     exiting++;
12201     if (exiting > 2) {
12202       /* Give up on clean exit */
12203       exit(status);
12204     }
12205     if (exiting > 1) {
12206       /* Keep trying for clean exit */
12207       return;
12208     }
12209
12210     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12211
12212     if (telnetISR != NULL) {
12213       RemoveInputSource(telnetISR);
12214     }
12215     if (icsPR != NoProc) {
12216       DestroyChildProcess(icsPR, TRUE);
12217     }
12218
12219     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12220     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12221
12222     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12223     /* make sure this other one finishes before killing it!                  */
12224     if(endingGame) { int count = 0;
12225         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12226         while(endingGame && count++ < 10) DoSleep(1);
12227         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12228     }
12229
12230     /* Kill off chess programs */
12231     if (first.pr != NoProc) {
12232         ExitAnalyzeMode();
12233
12234         DoSleep( appData.delayBeforeQuit );
12235         SendToProgram("quit\n", &first);
12236         DoSleep( appData.delayAfterQuit );
12237         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12238     }
12239     if (second.pr != NoProc) {
12240         DoSleep( appData.delayBeforeQuit );
12241         SendToProgram("quit\n", &second);
12242         DoSleep( appData.delayAfterQuit );
12243         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12244     }
12245     if (first.isr != NULL) {
12246         RemoveInputSource(first.isr);
12247     }
12248     if (second.isr != NULL) {
12249         RemoveInputSource(second.isr);
12250     }
12251
12252     ShutDownFrontEnd();
12253     exit(status);
12254 }
12255
12256 void
12257 PauseEvent()
12258 {
12259     if (appData.debugMode)
12260         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12261     if (pausing) {
12262         pausing = FALSE;
12263         ModeHighlight();
12264         if (gameMode == MachinePlaysWhite ||
12265             gameMode == MachinePlaysBlack) {
12266             StartClocks();
12267         } else {
12268             DisplayBothClocks();
12269         }
12270         if (gameMode == PlayFromGameFile) {
12271             if (appData.timeDelay >= 0)
12272                 AutoPlayGameLoop();
12273         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12274             Reset(FALSE, TRUE);
12275             SendToICS(ics_prefix);
12276             SendToICS("refresh\n");
12277         } else if (currentMove < forwardMostMove) {
12278             ForwardInner(forwardMostMove);
12279         }
12280         pauseExamInvalid = FALSE;
12281     } else {
12282         switch (gameMode) {
12283           default:
12284             return;
12285           case IcsExamining:
12286             pauseExamForwardMostMove = forwardMostMove;
12287             pauseExamInvalid = FALSE;
12288             /* fall through */
12289           case IcsObserving:
12290           case IcsPlayingWhite:
12291           case IcsPlayingBlack:
12292             pausing = TRUE;
12293             ModeHighlight();
12294             return;
12295           case PlayFromGameFile:
12296             (void) StopLoadGameTimer();
12297             pausing = TRUE;
12298             ModeHighlight();
12299             break;
12300           case BeginningOfGame:
12301             if (appData.icsActive) return;
12302             /* else fall through */
12303           case MachinePlaysWhite:
12304           case MachinePlaysBlack:
12305           case TwoMachinesPlay:
12306             if (forwardMostMove == 0)
12307               return;           /* don't pause if no one has moved */
12308             if ((gameMode == MachinePlaysWhite &&
12309                  !WhiteOnMove(forwardMostMove)) ||
12310                 (gameMode == MachinePlaysBlack &&
12311                  WhiteOnMove(forwardMostMove))) {
12312                 StopClocks();
12313             }
12314             pausing = TRUE;
12315             ModeHighlight();
12316             break;
12317         }
12318     }
12319 }
12320
12321 void
12322 EditCommentEvent()
12323 {
12324     char title[MSG_SIZ];
12325
12326     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12327       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12328     } else {
12329       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12330                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12331                parseList[currentMove - 1]);
12332     }
12333
12334     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12335 }
12336
12337
12338 void
12339 EditTagsEvent()
12340 {
12341     char *tags = PGNTags(&gameInfo);
12342     bookUp = FALSE;
12343     EditTagsPopUp(tags, NULL);
12344     free(tags);
12345 }
12346
12347 void
12348 AnalyzeModeEvent()
12349 {
12350     if (appData.noChessProgram || gameMode == AnalyzeMode)
12351       return;
12352
12353     if (gameMode != AnalyzeFile) {
12354         if (!appData.icsEngineAnalyze) {
12355                EditGameEvent();
12356                if (gameMode != EditGame) return;
12357         }
12358         ResurrectChessProgram();
12359         SendToProgram("analyze\n", &first);
12360         first.analyzing = TRUE;
12361         /*first.maybeThinking = TRUE;*/
12362         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12363         EngineOutputPopUp();
12364     }
12365     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12366     pausing = FALSE;
12367     ModeHighlight();
12368     SetGameInfo();
12369
12370     StartAnalysisClock();
12371     GetTimeMark(&lastNodeCountTime);
12372     lastNodeCount = 0;
12373 }
12374
12375 void
12376 AnalyzeFileEvent()
12377 {
12378     if (appData.noChessProgram || gameMode == AnalyzeFile)
12379       return;
12380
12381     if (gameMode != AnalyzeMode) {
12382         EditGameEvent();
12383         if (gameMode != EditGame) return;
12384         ResurrectChessProgram();
12385         SendToProgram("analyze\n", &first);
12386         first.analyzing = TRUE;
12387         /*first.maybeThinking = TRUE;*/
12388         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12389         EngineOutputPopUp();
12390     }
12391     gameMode = AnalyzeFile;
12392     pausing = FALSE;
12393     ModeHighlight();
12394     SetGameInfo();
12395
12396     StartAnalysisClock();
12397     GetTimeMark(&lastNodeCountTime);
12398     lastNodeCount = 0;
12399 }
12400
12401 void
12402 MachineWhiteEvent()
12403 {
12404     char buf[MSG_SIZ];
12405     char *bookHit = NULL;
12406
12407     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12408       return;
12409
12410
12411     if (gameMode == PlayFromGameFile ||
12412         gameMode == TwoMachinesPlay  ||
12413         gameMode == Training         ||
12414         gameMode == AnalyzeMode      ||
12415         gameMode == EndOfGame)
12416         EditGameEvent();
12417
12418     if (gameMode == EditPosition)
12419         EditPositionDone(TRUE);
12420
12421     if (!WhiteOnMove(currentMove)) {
12422         DisplayError(_("It is not White's turn"), 0);
12423         return;
12424     }
12425
12426     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12427       ExitAnalyzeMode();
12428
12429     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12430         gameMode == AnalyzeFile)
12431         TruncateGame();
12432
12433     ResurrectChessProgram();    /* in case it isn't running */
12434     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12435         gameMode = MachinePlaysWhite;
12436         ResetClocks();
12437     } else
12438     gameMode = MachinePlaysWhite;
12439     pausing = FALSE;
12440     ModeHighlight();
12441     SetGameInfo();
12442     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12443     DisplayTitle(buf);
12444     if (first.sendName) {
12445       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12446       SendToProgram(buf, &first);
12447     }
12448     if (first.sendTime) {
12449       if (first.useColors) {
12450         SendToProgram("black\n", &first); /*gnu kludge*/
12451       }
12452       SendTimeRemaining(&first, TRUE);
12453     }
12454     if (first.useColors) {
12455       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12456     }
12457     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12458     SetMachineThinkingEnables();
12459     first.maybeThinking = TRUE;
12460     StartClocks();
12461     firstMove = FALSE;
12462
12463     if (appData.autoFlipView && !flipView) {
12464       flipView = !flipView;
12465       DrawPosition(FALSE, NULL);
12466       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12467     }
12468
12469     if(bookHit) { // [HGM] book: simulate book reply
12470         static char bookMove[MSG_SIZ]; // a bit generous?
12471
12472         programStats.nodes = programStats.depth = programStats.time =
12473         programStats.score = programStats.got_only_move = 0;
12474         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12475
12476         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12477         strcat(bookMove, bookHit);
12478         HandleMachineMove(bookMove, &first);
12479     }
12480 }
12481
12482 void
12483 MachineBlackEvent()
12484 {
12485   char buf[MSG_SIZ];
12486   char *bookHit = NULL;
12487
12488     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12489         return;
12490
12491
12492     if (gameMode == PlayFromGameFile ||
12493         gameMode == TwoMachinesPlay  ||
12494         gameMode == Training         ||
12495         gameMode == AnalyzeMode      ||
12496         gameMode == EndOfGame)
12497         EditGameEvent();
12498
12499     if (gameMode == EditPosition)
12500         EditPositionDone(TRUE);
12501
12502     if (WhiteOnMove(currentMove)) {
12503         DisplayError(_("It is not Black's turn"), 0);
12504         return;
12505     }
12506
12507     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12508       ExitAnalyzeMode();
12509
12510     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12511         gameMode == AnalyzeFile)
12512         TruncateGame();
12513
12514     ResurrectChessProgram();    /* in case it isn't running */
12515     gameMode = MachinePlaysBlack;
12516     pausing = FALSE;
12517     ModeHighlight();
12518     SetGameInfo();
12519     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12520     DisplayTitle(buf);
12521     if (first.sendName) {
12522       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12523       SendToProgram(buf, &first);
12524     }
12525     if (first.sendTime) {
12526       if (first.useColors) {
12527         SendToProgram("white\n", &first); /*gnu kludge*/
12528       }
12529       SendTimeRemaining(&first, FALSE);
12530     }
12531     if (first.useColors) {
12532       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12533     }
12534     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12535     SetMachineThinkingEnables();
12536     first.maybeThinking = TRUE;
12537     StartClocks();
12538
12539     if (appData.autoFlipView && flipView) {
12540       flipView = !flipView;
12541       DrawPosition(FALSE, NULL);
12542       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12543     }
12544     if(bookHit) { // [HGM] book: simulate book reply
12545         static char bookMove[MSG_SIZ]; // a bit generous?
12546
12547         programStats.nodes = programStats.depth = programStats.time =
12548         programStats.score = programStats.got_only_move = 0;
12549         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12550
12551         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12552         strcat(bookMove, bookHit);
12553         HandleMachineMove(bookMove, &first);
12554     }
12555 }
12556
12557
12558 void
12559 DisplayTwoMachinesTitle()
12560 {
12561     char buf[MSG_SIZ];
12562     if (appData.matchGames > 0) {
12563         if (first.twoMachinesColor[0] == 'w') {
12564           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12565                    gameInfo.white, gameInfo.black,
12566                    first.matchWins, second.matchWins,
12567                    matchGame - 1 - (first.matchWins + second.matchWins));
12568         } else {
12569           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12570                    gameInfo.white, gameInfo.black,
12571                    second.matchWins, first.matchWins,
12572                    matchGame - 1 - (first.matchWins + second.matchWins));
12573         }
12574     } else {
12575       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12576     }
12577     DisplayTitle(buf);
12578 }
12579
12580 void
12581 SettingsMenuIfReady()
12582 {
12583   if (second.lastPing != second.lastPong) {
12584     DisplayMessage("", _("Waiting for second chess program"));
12585     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12586     return;
12587   }
12588   ThawUI();
12589   DisplayMessage("", "");
12590   SettingsPopUp(&second);
12591 }
12592
12593 int
12594 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12595 {
12596     char buf[MSG_SIZ];
12597     if (cps->pr == NULL) {
12598         StartChessProgram(cps);
12599         if (cps->protocolVersion == 1) {
12600           retry();
12601         } else {
12602           /* kludge: allow timeout for initial "feature" command */
12603           FreezeUI();
12604           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12605           DisplayMessage("", buf);
12606           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12607         }
12608         return 1;
12609     }
12610     return 0;
12611 }
12612
12613 void
12614 TwoMachinesEvent P((void))
12615 {
12616     int i;
12617     char buf[MSG_SIZ];
12618     ChessProgramState *onmove;
12619     char *bookHit = NULL;
12620     static int stalling = 0;
12621     TimeMark now;
12622     long wait;
12623
12624     if (appData.noChessProgram) return;
12625
12626     switch (gameMode) {
12627       case TwoMachinesPlay:
12628         return;
12629       case MachinePlaysWhite:
12630       case MachinePlaysBlack:
12631         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12632             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12633             return;
12634         }
12635         /* fall through */
12636       case BeginningOfGame:
12637       case PlayFromGameFile:
12638       case EndOfGame:
12639         EditGameEvent();
12640         if (gameMode != EditGame) return;
12641         break;
12642       case EditPosition:
12643         EditPositionDone(TRUE);
12644         break;
12645       case AnalyzeMode:
12646       case AnalyzeFile:
12647         ExitAnalyzeMode();
12648         break;
12649       case EditGame:
12650       default:
12651         break;
12652     }
12653
12654 //    forwardMostMove = currentMove;
12655     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12656
12657     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12658
12659     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12660     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12661       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12662       return;
12663     }
12664     if(!stalling) {
12665       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12666       SendToProgram("force\n", &second);
12667       stalling = 1;
12668       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12669       return;
12670     }
12671     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12672     if(appData.matchPause>10000 || appData.matchPause<10)
12673                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12674     wait = SubtractTimeMarks(&now, &pauseStart);
12675     if(wait < appData.matchPause) {
12676         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12677         return;
12678     }
12679     stalling = 0;
12680     DisplayMessage("", "");
12681     if (startedFromSetupPosition) {
12682         SendBoard(&second, backwardMostMove);
12683     if (appData.debugMode) {
12684         fprintf(debugFP, "Two Machines\n");
12685     }
12686     }
12687     for (i = backwardMostMove; i < forwardMostMove; i++) {
12688         SendMoveToProgram(i, &second);
12689     }
12690
12691     gameMode = TwoMachinesPlay;
12692     pausing = FALSE;
12693     ModeHighlight();
12694     SetGameInfo();
12695     DisplayTwoMachinesTitle();
12696     firstMove = TRUE;
12697     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12698         onmove = &first;
12699     } else {
12700         onmove = &second;
12701     }
12702     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12703     SendToProgram(first.computerString, &first);
12704     if (first.sendName) {
12705       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12706       SendToProgram(buf, &first);
12707     }
12708     SendToProgram(second.computerString, &second);
12709     if (second.sendName) {
12710       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12711       SendToProgram(buf, &second);
12712     }
12713
12714     ResetClocks();
12715     if (!first.sendTime || !second.sendTime) {
12716         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12717         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12718     }
12719     if (onmove->sendTime) {
12720       if (onmove->useColors) {
12721         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12722       }
12723       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12724     }
12725     if (onmove->useColors) {
12726       SendToProgram(onmove->twoMachinesColor, onmove);
12727     }
12728     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12729 //    SendToProgram("go\n", onmove);
12730     onmove->maybeThinking = TRUE;
12731     SetMachineThinkingEnables();
12732
12733     StartClocks();
12734
12735     if(bookHit) { // [HGM] book: simulate book reply
12736         static char bookMove[MSG_SIZ]; // a bit generous?
12737
12738         programStats.nodes = programStats.depth = programStats.time =
12739         programStats.score = programStats.got_only_move = 0;
12740         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12741
12742         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12743         strcat(bookMove, bookHit);
12744         savedMessage = bookMove; // args for deferred call
12745         savedState = onmove;
12746         ScheduleDelayedEvent(DeferredBookMove, 1);
12747     }
12748 }
12749
12750 void
12751 TrainingEvent()
12752 {
12753     if (gameMode == Training) {
12754       SetTrainingModeOff();
12755       gameMode = PlayFromGameFile;
12756       DisplayMessage("", _("Training mode off"));
12757     } else {
12758       gameMode = Training;
12759       animateTraining = appData.animate;
12760
12761       /* make sure we are not already at the end of the game */
12762       if (currentMove < forwardMostMove) {
12763         SetTrainingModeOn();
12764         DisplayMessage("", _("Training mode on"));
12765       } else {
12766         gameMode = PlayFromGameFile;
12767         DisplayError(_("Already at end of game"), 0);
12768       }
12769     }
12770     ModeHighlight();
12771 }
12772
12773 void
12774 IcsClientEvent()
12775 {
12776     if (!appData.icsActive) return;
12777     switch (gameMode) {
12778       case IcsPlayingWhite:
12779       case IcsPlayingBlack:
12780       case IcsObserving:
12781       case IcsIdle:
12782       case BeginningOfGame:
12783       case IcsExamining:
12784         return;
12785
12786       case EditGame:
12787         break;
12788
12789       case EditPosition:
12790         EditPositionDone(TRUE);
12791         break;
12792
12793       case AnalyzeMode:
12794       case AnalyzeFile:
12795         ExitAnalyzeMode();
12796         break;
12797
12798       default:
12799         EditGameEvent();
12800         break;
12801     }
12802
12803     gameMode = IcsIdle;
12804     ModeHighlight();
12805     return;
12806 }
12807
12808
12809 void
12810 EditGameEvent()
12811 {
12812     int i;
12813
12814     switch (gameMode) {
12815       case Training:
12816         SetTrainingModeOff();
12817         break;
12818       case MachinePlaysWhite:
12819       case MachinePlaysBlack:
12820       case BeginningOfGame:
12821         SendToProgram("force\n", &first);
12822         SetUserThinkingEnables();
12823         break;
12824       case PlayFromGameFile:
12825         (void) StopLoadGameTimer();
12826         if (gameFileFP != NULL) {
12827             gameFileFP = NULL;
12828         }
12829         break;
12830       case EditPosition:
12831         EditPositionDone(TRUE);
12832         break;
12833       case AnalyzeMode:
12834       case AnalyzeFile:
12835         ExitAnalyzeMode();
12836         SendToProgram("force\n", &first);
12837         break;
12838       case TwoMachinesPlay:
12839         GameEnds(EndOfFile, NULL, GE_PLAYER);
12840         ResurrectChessProgram();
12841         SetUserThinkingEnables();
12842         break;
12843       case EndOfGame:
12844         ResurrectChessProgram();
12845         break;
12846       case IcsPlayingBlack:
12847       case IcsPlayingWhite:
12848         DisplayError(_("Warning: You are still playing a game"), 0);
12849         break;
12850       case IcsObserving:
12851         DisplayError(_("Warning: You are still observing a game"), 0);
12852         break;
12853       case IcsExamining:
12854         DisplayError(_("Warning: You are still examining a game"), 0);
12855         break;
12856       case IcsIdle:
12857         break;
12858       case EditGame:
12859       default:
12860         return;
12861     }
12862
12863     pausing = FALSE;
12864     StopClocks();
12865     first.offeredDraw = second.offeredDraw = 0;
12866
12867     if (gameMode == PlayFromGameFile) {
12868         whiteTimeRemaining = timeRemaining[0][currentMove];
12869         blackTimeRemaining = timeRemaining[1][currentMove];
12870         DisplayTitle("");
12871     }
12872
12873     if (gameMode == MachinePlaysWhite ||
12874         gameMode == MachinePlaysBlack ||
12875         gameMode == TwoMachinesPlay ||
12876         gameMode == EndOfGame) {
12877         i = forwardMostMove;
12878         while (i > currentMove) {
12879             SendToProgram("undo\n", &first);
12880             i--;
12881         }
12882         whiteTimeRemaining = timeRemaining[0][currentMove];
12883         blackTimeRemaining = timeRemaining[1][currentMove];
12884         DisplayBothClocks();
12885         if (whiteFlag || blackFlag) {
12886             whiteFlag = blackFlag = 0;
12887         }
12888         DisplayTitle("");
12889     }
12890
12891     gameMode = EditGame;
12892     ModeHighlight();
12893     SetGameInfo();
12894 }
12895
12896
12897 void
12898 EditPositionEvent()
12899 {
12900     if (gameMode == EditPosition) {
12901         EditGameEvent();
12902         return;
12903     }
12904
12905     EditGameEvent();
12906     if (gameMode != EditGame) return;
12907
12908     gameMode = EditPosition;
12909     ModeHighlight();
12910     SetGameInfo();
12911     if (currentMove > 0)
12912       CopyBoard(boards[0], boards[currentMove]);
12913
12914     blackPlaysFirst = !WhiteOnMove(currentMove);
12915     ResetClocks();
12916     currentMove = forwardMostMove = backwardMostMove = 0;
12917     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12918     DisplayMove(-1);
12919 }
12920
12921 void
12922 ExitAnalyzeMode()
12923 {
12924     /* [DM] icsEngineAnalyze - possible call from other functions */
12925     if (appData.icsEngineAnalyze) {
12926         appData.icsEngineAnalyze = FALSE;
12927
12928         DisplayMessage("",_("Close ICS engine analyze..."));
12929     }
12930     if (first.analysisSupport && first.analyzing) {
12931       SendToProgram("exit\n", &first);
12932       first.analyzing = FALSE;
12933     }
12934     thinkOutput[0] = NULLCHAR;
12935 }
12936
12937 void
12938 EditPositionDone(Boolean fakeRights)
12939 {
12940     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12941
12942     startedFromSetupPosition = TRUE;
12943     InitChessProgram(&first, FALSE);
12944     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12945       boards[0][EP_STATUS] = EP_NONE;
12946       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12947     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12948         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12949         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12950       } else boards[0][CASTLING][2] = NoRights;
12951     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12952         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12953         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12954       } else boards[0][CASTLING][5] = NoRights;
12955     }
12956     SendToProgram("force\n", &first);
12957     if (blackPlaysFirst) {
12958         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12959         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12960         currentMove = forwardMostMove = backwardMostMove = 1;
12961         CopyBoard(boards[1], boards[0]);
12962     } else {
12963         currentMove = forwardMostMove = backwardMostMove = 0;
12964     }
12965     SendBoard(&first, forwardMostMove);
12966     if (appData.debugMode) {
12967         fprintf(debugFP, "EditPosDone\n");
12968     }
12969     DisplayTitle("");
12970     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12971     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12972     gameMode = EditGame;
12973     ModeHighlight();
12974     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12975     ClearHighlights(); /* [AS] */
12976 }
12977
12978 /* Pause for `ms' milliseconds */
12979 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12980 void
12981 TimeDelay(ms)
12982      long ms;
12983 {
12984     TimeMark m1, m2;
12985
12986     GetTimeMark(&m1);
12987     do {
12988         GetTimeMark(&m2);
12989     } while (SubtractTimeMarks(&m2, &m1) < ms);
12990 }
12991
12992 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12993 void
12994 SendMultiLineToICS(buf)
12995      char *buf;
12996 {
12997     char temp[MSG_SIZ+1], *p;
12998     int len;
12999
13000     len = strlen(buf);
13001     if (len > MSG_SIZ)
13002       len = MSG_SIZ;
13003
13004     strncpy(temp, buf, len);
13005     temp[len] = 0;
13006
13007     p = temp;
13008     while (*p) {
13009         if (*p == '\n' || *p == '\r')
13010           *p = ' ';
13011         ++p;
13012     }
13013
13014     strcat(temp, "\n");
13015     SendToICS(temp);
13016     SendToPlayer(temp, strlen(temp));
13017 }
13018
13019 void
13020 SetWhiteToPlayEvent()
13021 {
13022     if (gameMode == EditPosition) {
13023         blackPlaysFirst = FALSE;
13024         DisplayBothClocks();    /* works because currentMove is 0 */
13025     } else if (gameMode == IcsExamining) {
13026         SendToICS(ics_prefix);
13027         SendToICS("tomove white\n");
13028     }
13029 }
13030
13031 void
13032 SetBlackToPlayEvent()
13033 {
13034     if (gameMode == EditPosition) {
13035         blackPlaysFirst = TRUE;
13036         currentMove = 1;        /* kludge */
13037         DisplayBothClocks();
13038         currentMove = 0;
13039     } else if (gameMode == IcsExamining) {
13040         SendToICS(ics_prefix);
13041         SendToICS("tomove black\n");
13042     }
13043 }
13044
13045 void
13046 EditPositionMenuEvent(selection, x, y)
13047      ChessSquare selection;
13048      int x, y;
13049 {
13050     char buf[MSG_SIZ];
13051     ChessSquare piece = boards[0][y][x];
13052
13053     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13054
13055     switch (selection) {
13056       case ClearBoard:
13057         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13058             SendToICS(ics_prefix);
13059             SendToICS("bsetup clear\n");
13060         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13061             SendToICS(ics_prefix);
13062             SendToICS("clearboard\n");
13063         } else {
13064             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13065                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13066                 for (y = 0; y < BOARD_HEIGHT; y++) {
13067                     if (gameMode == IcsExamining) {
13068                         if (boards[currentMove][y][x] != EmptySquare) {
13069                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13070                                     AAA + x, ONE + y);
13071                             SendToICS(buf);
13072                         }
13073                     } else {
13074                         boards[0][y][x] = p;
13075                     }
13076                 }
13077             }
13078         }
13079         if (gameMode == EditPosition) {
13080             DrawPosition(FALSE, boards[0]);
13081         }
13082         break;
13083
13084       case WhitePlay:
13085         SetWhiteToPlayEvent();
13086         break;
13087
13088       case BlackPlay:
13089         SetBlackToPlayEvent();
13090         break;
13091
13092       case EmptySquare:
13093         if (gameMode == IcsExamining) {
13094             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13095             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13096             SendToICS(buf);
13097         } else {
13098             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13099                 if(x == BOARD_LEFT-2) {
13100                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13101                     boards[0][y][1] = 0;
13102                 } else
13103                 if(x == BOARD_RGHT+1) {
13104                     if(y >= gameInfo.holdingsSize) break;
13105                     boards[0][y][BOARD_WIDTH-2] = 0;
13106                 } else break;
13107             }
13108             boards[0][y][x] = EmptySquare;
13109             DrawPosition(FALSE, boards[0]);
13110         }
13111         break;
13112
13113       case PromotePiece:
13114         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13115            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13116             selection = (ChessSquare) (PROMOTED piece);
13117         } else if(piece == EmptySquare) selection = WhiteSilver;
13118         else selection = (ChessSquare)((int)piece - 1);
13119         goto defaultlabel;
13120
13121       case DemotePiece:
13122         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13123            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13124             selection = (ChessSquare) (DEMOTED piece);
13125         } else if(piece == EmptySquare) selection = BlackSilver;
13126         else selection = (ChessSquare)((int)piece + 1);
13127         goto defaultlabel;
13128
13129       case WhiteQueen:
13130       case BlackQueen:
13131         if(gameInfo.variant == VariantShatranj ||
13132            gameInfo.variant == VariantXiangqi  ||
13133            gameInfo.variant == VariantCourier  ||
13134            gameInfo.variant == VariantMakruk     )
13135             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13136         goto defaultlabel;
13137
13138       case WhiteKing:
13139       case BlackKing:
13140         if(gameInfo.variant == VariantXiangqi)
13141             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13142         if(gameInfo.variant == VariantKnightmate)
13143             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13144       default:
13145         defaultlabel:
13146         if (gameMode == IcsExamining) {
13147             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13148             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13149                      PieceToChar(selection), AAA + x, ONE + y);
13150             SendToICS(buf);
13151         } else {
13152             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13153                 int n;
13154                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13155                     n = PieceToNumber(selection - BlackPawn);
13156                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13157                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13158                     boards[0][BOARD_HEIGHT-1-n][1]++;
13159                 } else
13160                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13161                     n = PieceToNumber(selection);
13162                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13163                     boards[0][n][BOARD_WIDTH-1] = selection;
13164                     boards[0][n][BOARD_WIDTH-2]++;
13165                 }
13166             } else
13167             boards[0][y][x] = selection;
13168             DrawPosition(TRUE, boards[0]);
13169         }
13170         break;
13171     }
13172 }
13173
13174
13175 void
13176 DropMenuEvent(selection, x, y)
13177      ChessSquare selection;
13178      int x, y;
13179 {
13180     ChessMove moveType;
13181
13182     switch (gameMode) {
13183       case IcsPlayingWhite:
13184       case MachinePlaysBlack:
13185         if (!WhiteOnMove(currentMove)) {
13186             DisplayMoveError(_("It is Black's turn"));
13187             return;
13188         }
13189         moveType = WhiteDrop;
13190         break;
13191       case IcsPlayingBlack:
13192       case MachinePlaysWhite:
13193         if (WhiteOnMove(currentMove)) {
13194             DisplayMoveError(_("It is White's turn"));
13195             return;
13196         }
13197         moveType = BlackDrop;
13198         break;
13199       case EditGame:
13200         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13201         break;
13202       default:
13203         return;
13204     }
13205
13206     if (moveType == BlackDrop && selection < BlackPawn) {
13207       selection = (ChessSquare) ((int) selection
13208                                  + (int) BlackPawn - (int) WhitePawn);
13209     }
13210     if (boards[currentMove][y][x] != EmptySquare) {
13211         DisplayMoveError(_("That square is occupied"));
13212         return;
13213     }
13214
13215     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13216 }
13217
13218 void
13219 AcceptEvent()
13220 {
13221     /* Accept a pending offer of any kind from opponent */
13222
13223     if (appData.icsActive) {
13224         SendToICS(ics_prefix);
13225         SendToICS("accept\n");
13226     } else if (cmailMsgLoaded) {
13227         if (currentMove == cmailOldMove &&
13228             commentList[cmailOldMove] != NULL &&
13229             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13230                    "Black offers a draw" : "White offers a draw")) {
13231             TruncateGame();
13232             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13233             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13234         } else {
13235             DisplayError(_("There is no pending offer on this move"), 0);
13236             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13237         }
13238     } else {
13239         /* Not used for offers from chess program */
13240     }
13241 }
13242
13243 void
13244 DeclineEvent()
13245 {
13246     /* Decline a pending offer of any kind from opponent */
13247
13248     if (appData.icsActive) {
13249         SendToICS(ics_prefix);
13250         SendToICS("decline\n");
13251     } else if (cmailMsgLoaded) {
13252         if (currentMove == cmailOldMove &&
13253             commentList[cmailOldMove] != NULL &&
13254             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13255                    "Black offers a draw" : "White offers a draw")) {
13256 #ifdef NOTDEF
13257             AppendComment(cmailOldMove, "Draw declined", TRUE);
13258             DisplayComment(cmailOldMove - 1, "Draw declined");
13259 #endif /*NOTDEF*/
13260         } else {
13261             DisplayError(_("There is no pending offer on this move"), 0);
13262         }
13263     } else {
13264         /* Not used for offers from chess program */
13265     }
13266 }
13267
13268 void
13269 RematchEvent()
13270 {
13271     /* Issue ICS rematch command */
13272     if (appData.icsActive) {
13273         SendToICS(ics_prefix);
13274         SendToICS("rematch\n");
13275     }
13276 }
13277
13278 void
13279 CallFlagEvent()
13280 {
13281     /* Call your opponent's flag (claim a win on time) */
13282     if (appData.icsActive) {
13283         SendToICS(ics_prefix);
13284         SendToICS("flag\n");
13285     } else {
13286         switch (gameMode) {
13287           default:
13288             return;
13289           case MachinePlaysWhite:
13290             if (whiteFlag) {
13291                 if (blackFlag)
13292                   GameEnds(GameIsDrawn, "Both players ran out of time",
13293                            GE_PLAYER);
13294                 else
13295                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13296             } else {
13297                 DisplayError(_("Your opponent is not out of time"), 0);
13298             }
13299             break;
13300           case MachinePlaysBlack:
13301             if (blackFlag) {
13302                 if (whiteFlag)
13303                   GameEnds(GameIsDrawn, "Both players ran out of time",
13304                            GE_PLAYER);
13305                 else
13306                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13307             } else {
13308                 DisplayError(_("Your opponent is not out of time"), 0);
13309             }
13310             break;
13311         }
13312     }
13313 }
13314
13315 void
13316 ClockClick(int which)
13317 {       // [HGM] code moved to back-end from winboard.c
13318         if(which) { // black clock
13319           if (gameMode == EditPosition || gameMode == IcsExamining) {
13320             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13321             SetBlackToPlayEvent();
13322           } else if (gameMode == EditGame || shiftKey) {
13323             AdjustClock(which, -1);
13324           } else if (gameMode == IcsPlayingWhite ||
13325                      gameMode == MachinePlaysBlack) {
13326             CallFlagEvent();
13327           }
13328         } else { // white clock
13329           if (gameMode == EditPosition || gameMode == IcsExamining) {
13330             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13331             SetWhiteToPlayEvent();
13332           } else if (gameMode == EditGame || shiftKey) {
13333             AdjustClock(which, -1);
13334           } else if (gameMode == IcsPlayingBlack ||
13335                    gameMode == MachinePlaysWhite) {
13336             CallFlagEvent();
13337           }
13338         }
13339 }
13340
13341 void
13342 DrawEvent()
13343 {
13344     /* Offer draw or accept pending draw offer from opponent */
13345
13346     if (appData.icsActive) {
13347         /* Note: tournament rules require draw offers to be
13348            made after you make your move but before you punch
13349            your clock.  Currently ICS doesn't let you do that;
13350            instead, you immediately punch your clock after making
13351            a move, but you can offer a draw at any time. */
13352
13353         SendToICS(ics_prefix);
13354         SendToICS("draw\n");
13355         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13356     } else if (cmailMsgLoaded) {
13357         if (currentMove == cmailOldMove &&
13358             commentList[cmailOldMove] != NULL &&
13359             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13360                    "Black offers a draw" : "White offers a draw")) {
13361             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13362             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13363         } else if (currentMove == cmailOldMove + 1) {
13364             char *offer = WhiteOnMove(cmailOldMove) ?
13365               "White offers a draw" : "Black offers a draw";
13366             AppendComment(currentMove, offer, TRUE);
13367             DisplayComment(currentMove - 1, offer);
13368             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13369         } else {
13370             DisplayError(_("You must make your move before offering a draw"), 0);
13371             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13372         }
13373     } else if (first.offeredDraw) {
13374         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13375     } else {
13376         if (first.sendDrawOffers) {
13377             SendToProgram("draw\n", &first);
13378             userOfferedDraw = TRUE;
13379         }
13380     }
13381 }
13382
13383 void
13384 AdjournEvent()
13385 {
13386     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13387
13388     if (appData.icsActive) {
13389         SendToICS(ics_prefix);
13390         SendToICS("adjourn\n");
13391     } else {
13392         /* Currently GNU Chess doesn't offer or accept Adjourns */
13393     }
13394 }
13395
13396
13397 void
13398 AbortEvent()
13399 {
13400     /* Offer Abort or accept pending Abort offer from opponent */
13401
13402     if (appData.icsActive) {
13403         SendToICS(ics_prefix);
13404         SendToICS("abort\n");
13405     } else {
13406         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13407     }
13408 }
13409
13410 void
13411 ResignEvent()
13412 {
13413     /* Resign.  You can do this even if it's not your turn. */
13414
13415     if (appData.icsActive) {
13416         SendToICS(ics_prefix);
13417         SendToICS("resign\n");
13418     } else {
13419         switch (gameMode) {
13420           case MachinePlaysWhite:
13421             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13422             break;
13423           case MachinePlaysBlack:
13424             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13425             break;
13426           case EditGame:
13427             if (cmailMsgLoaded) {
13428                 TruncateGame();
13429                 if (WhiteOnMove(cmailOldMove)) {
13430                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13431                 } else {
13432                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13433                 }
13434                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13435             }
13436             break;
13437           default:
13438             break;
13439         }
13440     }
13441 }
13442
13443
13444 void
13445 StopObservingEvent()
13446 {
13447     /* Stop observing current games */
13448     SendToICS(ics_prefix);
13449     SendToICS("unobserve\n");
13450 }
13451
13452 void
13453 StopExaminingEvent()
13454 {
13455     /* Stop observing current game */
13456     SendToICS(ics_prefix);
13457     SendToICS("unexamine\n");
13458 }
13459
13460 void
13461 ForwardInner(target)
13462      int target;
13463 {
13464     int limit;
13465
13466     if (appData.debugMode)
13467         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13468                 target, currentMove, forwardMostMove);
13469
13470     if (gameMode == EditPosition)
13471       return;
13472
13473     if (gameMode == PlayFromGameFile && !pausing)
13474       PauseEvent();
13475
13476     if (gameMode == IcsExamining && pausing)
13477       limit = pauseExamForwardMostMove;
13478     else
13479       limit = forwardMostMove;
13480
13481     if (target > limit) target = limit;
13482
13483     if (target > 0 && moveList[target - 1][0]) {
13484         int fromX, fromY, toX, toY;
13485         toX = moveList[target - 1][2] - AAA;
13486         toY = moveList[target - 1][3] - ONE;
13487         if (moveList[target - 1][1] == '@') {
13488             if (appData.highlightLastMove) {
13489                 SetHighlights(-1, -1, toX, toY);
13490             }
13491         } else {
13492             fromX = moveList[target - 1][0] - AAA;
13493             fromY = moveList[target - 1][1] - ONE;
13494             if (target == currentMove + 1) {
13495                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13496             }
13497             if (appData.highlightLastMove) {
13498                 SetHighlights(fromX, fromY, toX, toY);
13499             }
13500         }
13501     }
13502     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13503         gameMode == Training || gameMode == PlayFromGameFile ||
13504         gameMode == AnalyzeFile) {
13505         while (currentMove < target) {
13506             SendMoveToProgram(currentMove++, &first);
13507         }
13508     } else {
13509         currentMove = target;
13510     }
13511
13512     if (gameMode == EditGame || gameMode == EndOfGame) {
13513         whiteTimeRemaining = timeRemaining[0][currentMove];
13514         blackTimeRemaining = timeRemaining[1][currentMove];
13515     }
13516     DisplayBothClocks();
13517     DisplayMove(currentMove - 1);
13518     DrawPosition(FALSE, boards[currentMove]);
13519     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13520     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13521         DisplayComment(currentMove - 1, commentList[currentMove]);
13522     }
13523     DisplayBook(currentMove);
13524 }
13525
13526
13527 void
13528 ForwardEvent()
13529 {
13530     if (gameMode == IcsExamining && !pausing) {
13531         SendToICS(ics_prefix);
13532         SendToICS("forward\n");
13533     } else {
13534         ForwardInner(currentMove + 1);
13535     }
13536 }
13537
13538 void
13539 ToEndEvent()
13540 {
13541     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13542         /* to optimze, we temporarily turn off analysis mode while we feed
13543          * the remaining moves to the engine. Otherwise we get analysis output
13544          * after each move.
13545          */
13546         if (first.analysisSupport) {
13547           SendToProgram("exit\nforce\n", &first);
13548           first.analyzing = FALSE;
13549         }
13550     }
13551
13552     if (gameMode == IcsExamining && !pausing) {
13553         SendToICS(ics_prefix);
13554         SendToICS("forward 999999\n");
13555     } else {
13556         ForwardInner(forwardMostMove);
13557     }
13558
13559     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13560         /* we have fed all the moves, so reactivate analysis mode */
13561         SendToProgram("analyze\n", &first);
13562         first.analyzing = TRUE;
13563         /*first.maybeThinking = TRUE;*/
13564         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13565     }
13566 }
13567
13568 void
13569 BackwardInner(target)
13570      int target;
13571 {
13572     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13573
13574     if (appData.debugMode)
13575         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13576                 target, currentMove, forwardMostMove);
13577
13578     if (gameMode == EditPosition) return;
13579     if (currentMove <= backwardMostMove) {
13580         ClearHighlights();
13581         DrawPosition(full_redraw, boards[currentMove]);
13582         return;
13583     }
13584     if (gameMode == PlayFromGameFile && !pausing)
13585       PauseEvent();
13586
13587     if (moveList[target][0]) {
13588         int fromX, fromY, toX, toY;
13589         toX = moveList[target][2] - AAA;
13590         toY = moveList[target][3] - ONE;
13591         if (moveList[target][1] == '@') {
13592             if (appData.highlightLastMove) {
13593                 SetHighlights(-1, -1, toX, toY);
13594             }
13595         } else {
13596             fromX = moveList[target][0] - AAA;
13597             fromY = moveList[target][1] - ONE;
13598             if (target == currentMove - 1) {
13599                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13600             }
13601             if (appData.highlightLastMove) {
13602                 SetHighlights(fromX, fromY, toX, toY);
13603             }
13604         }
13605     }
13606     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13607         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13608         while (currentMove > target) {
13609             SendToProgram("undo\n", &first);
13610             currentMove--;
13611         }
13612     } else {
13613         currentMove = target;
13614     }
13615
13616     if (gameMode == EditGame || gameMode == EndOfGame) {
13617         whiteTimeRemaining = timeRemaining[0][currentMove];
13618         blackTimeRemaining = timeRemaining[1][currentMove];
13619     }
13620     DisplayBothClocks();
13621     DisplayMove(currentMove - 1);
13622     DrawPosition(full_redraw, boards[currentMove]);
13623     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13624     // [HGM] PV info: routine tests if comment empty
13625     DisplayComment(currentMove - 1, commentList[currentMove]);
13626     DisplayBook(currentMove);
13627 }
13628
13629 void
13630 BackwardEvent()
13631 {
13632     if (gameMode == IcsExamining && !pausing) {
13633         SendToICS(ics_prefix);
13634         SendToICS("backward\n");
13635     } else {
13636         BackwardInner(currentMove - 1);
13637     }
13638 }
13639
13640 void
13641 ToStartEvent()
13642 {
13643     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13644         /* to optimize, we temporarily turn off analysis mode while we undo
13645          * all the moves. Otherwise we get analysis output after each undo.
13646          */
13647         if (first.analysisSupport) {
13648           SendToProgram("exit\nforce\n", &first);
13649           first.analyzing = FALSE;
13650         }
13651     }
13652
13653     if (gameMode == IcsExamining && !pausing) {
13654         SendToICS(ics_prefix);
13655         SendToICS("backward 999999\n");
13656     } else {
13657         BackwardInner(backwardMostMove);
13658     }
13659
13660     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13661         /* we have fed all the moves, so reactivate analysis mode */
13662         SendToProgram("analyze\n", &first);
13663         first.analyzing = TRUE;
13664         /*first.maybeThinking = TRUE;*/
13665         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13666     }
13667 }
13668
13669 void
13670 ToNrEvent(int to)
13671 {
13672   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13673   if (to >= forwardMostMove) to = forwardMostMove;
13674   if (to <= backwardMostMove) to = backwardMostMove;
13675   if (to < currentMove) {
13676     BackwardInner(to);
13677   } else {
13678     ForwardInner(to);
13679   }
13680 }
13681
13682 void
13683 RevertEvent(Boolean annotate)
13684 {
13685     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13686         return;
13687     }
13688     if (gameMode != IcsExamining) {
13689         DisplayError(_("You are not examining a game"), 0);
13690         return;
13691     }
13692     if (pausing) {
13693         DisplayError(_("You can't revert while pausing"), 0);
13694         return;
13695     }
13696     SendToICS(ics_prefix);
13697     SendToICS("revert\n");
13698 }
13699
13700 void
13701 RetractMoveEvent()
13702 {
13703     switch (gameMode) {
13704       case MachinePlaysWhite:
13705       case MachinePlaysBlack:
13706         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13707             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13708             return;
13709         }
13710         if (forwardMostMove < 2) return;
13711         currentMove = forwardMostMove = forwardMostMove - 2;
13712         whiteTimeRemaining = timeRemaining[0][currentMove];
13713         blackTimeRemaining = timeRemaining[1][currentMove];
13714         DisplayBothClocks();
13715         DisplayMove(currentMove - 1);
13716         ClearHighlights();/*!! could figure this out*/
13717         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13718         SendToProgram("remove\n", &first);
13719         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13720         break;
13721
13722       case BeginningOfGame:
13723       default:
13724         break;
13725
13726       case IcsPlayingWhite:
13727       case IcsPlayingBlack:
13728         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13729             SendToICS(ics_prefix);
13730             SendToICS("takeback 2\n");
13731         } else {
13732             SendToICS(ics_prefix);
13733             SendToICS("takeback 1\n");
13734         }
13735         break;
13736     }
13737 }
13738
13739 void
13740 MoveNowEvent()
13741 {
13742     ChessProgramState *cps;
13743
13744     switch (gameMode) {
13745       case MachinePlaysWhite:
13746         if (!WhiteOnMove(forwardMostMove)) {
13747             DisplayError(_("It is your turn"), 0);
13748             return;
13749         }
13750         cps = &first;
13751         break;
13752       case MachinePlaysBlack:
13753         if (WhiteOnMove(forwardMostMove)) {
13754             DisplayError(_("It is your turn"), 0);
13755             return;
13756         }
13757         cps = &first;
13758         break;
13759       case TwoMachinesPlay:
13760         if (WhiteOnMove(forwardMostMove) ==
13761             (first.twoMachinesColor[0] == 'w')) {
13762             cps = &first;
13763         } else {
13764             cps = &second;
13765         }
13766         break;
13767       case BeginningOfGame:
13768       default:
13769         return;
13770     }
13771     SendToProgram("?\n", cps);
13772 }
13773
13774 void
13775 TruncateGameEvent()
13776 {
13777     EditGameEvent();
13778     if (gameMode != EditGame) return;
13779     TruncateGame();
13780 }
13781
13782 void
13783 TruncateGame()
13784 {
13785     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13786     if (forwardMostMove > currentMove) {
13787         if (gameInfo.resultDetails != NULL) {
13788             free(gameInfo.resultDetails);
13789             gameInfo.resultDetails = NULL;
13790             gameInfo.result = GameUnfinished;
13791         }
13792         forwardMostMove = currentMove;
13793         HistorySet(parseList, backwardMostMove, forwardMostMove,
13794                    currentMove-1);
13795     }
13796 }
13797
13798 void
13799 HintEvent()
13800 {
13801     if (appData.noChessProgram) return;
13802     switch (gameMode) {
13803       case MachinePlaysWhite:
13804         if (WhiteOnMove(forwardMostMove)) {
13805             DisplayError(_("Wait until your turn"), 0);
13806             return;
13807         }
13808         break;
13809       case BeginningOfGame:
13810       case MachinePlaysBlack:
13811         if (!WhiteOnMove(forwardMostMove)) {
13812             DisplayError(_("Wait until your turn"), 0);
13813             return;
13814         }
13815         break;
13816       default:
13817         DisplayError(_("No hint available"), 0);
13818         return;
13819     }
13820     SendToProgram("hint\n", &first);
13821     hintRequested = TRUE;
13822 }
13823
13824 void
13825 BookEvent()
13826 {
13827     if (appData.noChessProgram) return;
13828     switch (gameMode) {
13829       case MachinePlaysWhite:
13830         if (WhiteOnMove(forwardMostMove)) {
13831             DisplayError(_("Wait until your turn"), 0);
13832             return;
13833         }
13834         break;
13835       case BeginningOfGame:
13836       case MachinePlaysBlack:
13837         if (!WhiteOnMove(forwardMostMove)) {
13838             DisplayError(_("Wait until your turn"), 0);
13839             return;
13840         }
13841         break;
13842       case EditPosition:
13843         EditPositionDone(TRUE);
13844         break;
13845       case TwoMachinesPlay:
13846         return;
13847       default:
13848         break;
13849     }
13850     SendToProgram("bk\n", &first);
13851     bookOutput[0] = NULLCHAR;
13852     bookRequested = TRUE;
13853 }
13854
13855 void
13856 AboutGameEvent()
13857 {
13858     char *tags = PGNTags(&gameInfo);
13859     TagsPopUp(tags, CmailMsg());
13860     free(tags);
13861 }
13862
13863 /* end button procedures */
13864
13865 void
13866 PrintPosition(fp, move)
13867      FILE *fp;
13868      int move;
13869 {
13870     int i, j;
13871
13872     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13873         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13874             char c = PieceToChar(boards[move][i][j]);
13875             fputc(c == 'x' ? '.' : c, fp);
13876             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13877         }
13878     }
13879     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13880       fprintf(fp, "white to play\n");
13881     else
13882       fprintf(fp, "black to play\n");
13883 }
13884
13885 void
13886 PrintOpponents(fp)
13887      FILE *fp;
13888 {
13889     if (gameInfo.white != NULL) {
13890         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13891     } else {
13892         fprintf(fp, "\n");
13893     }
13894 }
13895
13896 /* Find last component of program's own name, using some heuristics */
13897 void
13898 TidyProgramName(prog, host, buf)
13899      char *prog, *host, buf[MSG_SIZ];
13900 {
13901     char *p, *q;
13902     int local = (strcmp(host, "localhost") == 0);
13903     while (!local && (p = strchr(prog, ';')) != NULL) {
13904         p++;
13905         while (*p == ' ') p++;
13906         prog = p;
13907     }
13908     if (*prog == '"' || *prog == '\'') {
13909         q = strchr(prog + 1, *prog);
13910     } else {
13911         q = strchr(prog, ' ');
13912     }
13913     if (q == NULL) q = prog + strlen(prog);
13914     p = q;
13915     while (p >= prog && *p != '/' && *p != '\\') p--;
13916     p++;
13917     if(p == prog && *p == '"') p++;
13918     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13919     memcpy(buf, p, q - p);
13920     buf[q - p] = NULLCHAR;
13921     if (!local) {
13922         strcat(buf, "@");
13923         strcat(buf, host);
13924     }
13925 }
13926
13927 char *
13928 TimeControlTagValue()
13929 {
13930     char buf[MSG_SIZ];
13931     if (!appData.clockMode) {
13932       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13933     } else if (movesPerSession > 0) {
13934       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13935     } else if (timeIncrement == 0) {
13936       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13937     } else {
13938       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13939     }
13940     return StrSave(buf);
13941 }
13942
13943 void
13944 SetGameInfo()
13945 {
13946     /* This routine is used only for certain modes */
13947     VariantClass v = gameInfo.variant;
13948     ChessMove r = GameUnfinished;
13949     char *p = NULL;
13950
13951     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13952         r = gameInfo.result;
13953         p = gameInfo.resultDetails;
13954         gameInfo.resultDetails = NULL;
13955     }
13956     ClearGameInfo(&gameInfo);
13957     gameInfo.variant = v;
13958
13959     switch (gameMode) {
13960       case MachinePlaysWhite:
13961         gameInfo.event = StrSave( appData.pgnEventHeader );
13962         gameInfo.site = StrSave(HostName());
13963         gameInfo.date = PGNDate();
13964         gameInfo.round = StrSave("-");
13965         gameInfo.white = StrSave(first.tidy);
13966         gameInfo.black = StrSave(UserName());
13967         gameInfo.timeControl = TimeControlTagValue();
13968         break;
13969
13970       case MachinePlaysBlack:
13971         gameInfo.event = StrSave( appData.pgnEventHeader );
13972         gameInfo.site = StrSave(HostName());
13973         gameInfo.date = PGNDate();
13974         gameInfo.round = StrSave("-");
13975         gameInfo.white = StrSave(UserName());
13976         gameInfo.black = StrSave(first.tidy);
13977         gameInfo.timeControl = TimeControlTagValue();
13978         break;
13979
13980       case TwoMachinesPlay:
13981         gameInfo.event = StrSave( appData.pgnEventHeader );
13982         gameInfo.site = StrSave(HostName());
13983         gameInfo.date = PGNDate();
13984         if (roundNr > 0) {
13985             char buf[MSG_SIZ];
13986             snprintf(buf, MSG_SIZ, "%d", roundNr);
13987             gameInfo.round = StrSave(buf);
13988         } else {
13989             gameInfo.round = StrSave("-");
13990         }
13991         if (first.twoMachinesColor[0] == 'w') {
13992             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13993             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13994         } else {
13995             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13996             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13997         }
13998         gameInfo.timeControl = TimeControlTagValue();
13999         break;
14000
14001       case EditGame:
14002         gameInfo.event = StrSave("Edited game");
14003         gameInfo.site = StrSave(HostName());
14004         gameInfo.date = PGNDate();
14005         gameInfo.round = StrSave("-");
14006         gameInfo.white = StrSave("-");
14007         gameInfo.black = StrSave("-");
14008         gameInfo.result = r;
14009         gameInfo.resultDetails = p;
14010         break;
14011
14012       case EditPosition:
14013         gameInfo.event = StrSave("Edited position");
14014         gameInfo.site = StrSave(HostName());
14015         gameInfo.date = PGNDate();
14016         gameInfo.round = StrSave("-");
14017         gameInfo.white = StrSave("-");
14018         gameInfo.black = StrSave("-");
14019         break;
14020
14021       case IcsPlayingWhite:
14022       case IcsPlayingBlack:
14023       case IcsObserving:
14024       case IcsExamining:
14025         break;
14026
14027       case PlayFromGameFile:
14028         gameInfo.event = StrSave("Game from non-PGN file");
14029         gameInfo.site = StrSave(HostName());
14030         gameInfo.date = PGNDate();
14031         gameInfo.round = StrSave("-");
14032         gameInfo.white = StrSave("?");
14033         gameInfo.black = StrSave("?");
14034         break;
14035
14036       default:
14037         break;
14038     }
14039 }
14040
14041 void
14042 ReplaceComment(index, text)
14043      int index;
14044      char *text;
14045 {
14046     int len;
14047     char *p;
14048     float score;
14049
14050     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14051        pvInfoList[index-1].depth == len &&
14052        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14053        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14054     while (*text == '\n') text++;
14055     len = strlen(text);
14056     while (len > 0 && text[len - 1] == '\n') len--;
14057
14058     if (commentList[index] != NULL)
14059       free(commentList[index]);
14060
14061     if (len == 0) {
14062         commentList[index] = NULL;
14063         return;
14064     }
14065   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14066       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14067       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14068     commentList[index] = (char *) malloc(len + 2);
14069     strncpy(commentList[index], text, len);
14070     commentList[index][len] = '\n';
14071     commentList[index][len + 1] = NULLCHAR;
14072   } else {
14073     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14074     char *p;
14075     commentList[index] = (char *) malloc(len + 7);
14076     safeStrCpy(commentList[index], "{\n", 3);
14077     safeStrCpy(commentList[index]+2, text, len+1);
14078     commentList[index][len+2] = NULLCHAR;
14079     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14080     strcat(commentList[index], "\n}\n");
14081   }
14082 }
14083
14084 void
14085 CrushCRs(text)
14086      char *text;
14087 {
14088   char *p = text;
14089   char *q = text;
14090   char ch;
14091
14092   do {
14093     ch = *p++;
14094     if (ch == '\r') continue;
14095     *q++ = ch;
14096   } while (ch != '\0');
14097 }
14098
14099 void
14100 AppendComment(index, text, addBraces)
14101      int index;
14102      char *text;
14103      Boolean addBraces; // [HGM] braces: tells if we should add {}
14104 {
14105     int oldlen, len;
14106     char *old;
14107
14108 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14109     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14110
14111     CrushCRs(text);
14112     while (*text == '\n') text++;
14113     len = strlen(text);
14114     while (len > 0 && text[len - 1] == '\n') len--;
14115
14116     if (len == 0) return;
14117
14118     if (commentList[index] != NULL) {
14119         old = commentList[index];
14120         oldlen = strlen(old);
14121         while(commentList[index][oldlen-1] ==  '\n')
14122           commentList[index][--oldlen] = NULLCHAR;
14123         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14124         safeStrCpy(commentList[index], old, oldlen + len + 6);
14125         free(old);
14126         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14127         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14128           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14129           while (*text == '\n') { text++; len--; }
14130           commentList[index][--oldlen] = NULLCHAR;
14131       }
14132         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14133         else          strcat(commentList[index], "\n");
14134         strcat(commentList[index], text);
14135         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14136         else          strcat(commentList[index], "\n");
14137     } else {
14138         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14139         if(addBraces)
14140           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14141         else commentList[index][0] = NULLCHAR;
14142         strcat(commentList[index], text);
14143         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14144         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14145     }
14146 }
14147
14148 static char * FindStr( char * text, char * sub_text )
14149 {
14150     char * result = strstr( text, sub_text );
14151
14152     if( result != NULL ) {
14153         result += strlen( sub_text );
14154     }
14155
14156     return result;
14157 }
14158
14159 /* [AS] Try to extract PV info from PGN comment */
14160 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14161 char *GetInfoFromComment( int index, char * text )
14162 {
14163     char * sep = text, *p;
14164
14165     if( text != NULL && index > 0 ) {
14166         int score = 0;
14167         int depth = 0;
14168         int time = -1, sec = 0, deci;
14169         char * s_eval = FindStr( text, "[%eval " );
14170         char * s_emt = FindStr( text, "[%emt " );
14171
14172         if( s_eval != NULL || s_emt != NULL ) {
14173             /* New style */
14174             char delim;
14175
14176             if( s_eval != NULL ) {
14177                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14178                     return text;
14179                 }
14180
14181                 if( delim != ']' ) {
14182                     return text;
14183                 }
14184             }
14185
14186             if( s_emt != NULL ) {
14187             }
14188                 return text;
14189         }
14190         else {
14191             /* We expect something like: [+|-]nnn.nn/dd */
14192             int score_lo = 0;
14193
14194             if(*text != '{') return text; // [HGM] braces: must be normal comment
14195
14196             sep = strchr( text, '/' );
14197             if( sep == NULL || sep < (text+4) ) {
14198                 return text;
14199             }
14200
14201             p = text;
14202             if(p[1] == '(') { // comment starts with PV
14203                p = strchr(p, ')'); // locate end of PV
14204                if(p == NULL || sep < p+5) return text;
14205                // at this point we have something like "{(.*) +0.23/6 ..."
14206                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14207                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14208                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14209             }
14210             time = -1; sec = -1; deci = -1;
14211             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14212                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14213                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14214                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14215                 return text;
14216             }
14217
14218             if( score_lo < 0 || score_lo >= 100 ) {
14219                 return text;
14220             }
14221
14222             if(sec >= 0) time = 600*time + 10*sec; else
14223             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14224
14225             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14226
14227             /* [HGM] PV time: now locate end of PV info */
14228             while( *++sep >= '0' && *sep <= '9'); // strip depth
14229             if(time >= 0)
14230             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14231             if(sec >= 0)
14232             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14233             if(deci >= 0)
14234             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14235             while(*sep == ' ') sep++;
14236         }
14237
14238         if( depth <= 0 ) {
14239             return text;
14240         }
14241
14242         if( time < 0 ) {
14243             time = -1;
14244         }
14245
14246         pvInfoList[index-1].depth = depth;
14247         pvInfoList[index-1].score = score;
14248         pvInfoList[index-1].time  = 10*time; // centi-sec
14249         if(*sep == '}') *sep = 0; else *--sep = '{';
14250         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14251     }
14252     return sep;
14253 }
14254
14255 void
14256 SendToProgram(message, cps)
14257      char *message;
14258      ChessProgramState *cps;
14259 {
14260     int count, outCount, error;
14261     char buf[MSG_SIZ];
14262
14263     if (cps->pr == NULL) return;
14264     Attention(cps);
14265
14266     if (appData.debugMode) {
14267         TimeMark now;
14268         GetTimeMark(&now);
14269         fprintf(debugFP, "%ld >%-6s: %s",
14270                 SubtractTimeMarks(&now, &programStartTime),
14271                 cps->which, message);
14272     }
14273
14274     count = strlen(message);
14275     outCount = OutputToProcess(cps->pr, message, count, &error);
14276     if (outCount < count && !exiting
14277                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14278       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14279       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14280         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14281             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14282                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14283                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14284                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14285             } else {
14286                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14287                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14288                 gameInfo.result = res;
14289             }
14290             gameInfo.resultDetails = StrSave(buf);
14291         }
14292         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14293         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14294     }
14295 }
14296
14297 void
14298 ReceiveFromProgram(isr, closure, message, count, error)
14299      InputSourceRef isr;
14300      VOIDSTAR closure;
14301      char *message;
14302      int count;
14303      int error;
14304 {
14305     char *end_str;
14306     char buf[MSG_SIZ];
14307     ChessProgramState *cps = (ChessProgramState *)closure;
14308
14309     if (isr != cps->isr) return; /* Killed intentionally */
14310     if (count <= 0) {
14311         if (count == 0) {
14312             RemoveInputSource(cps->isr);
14313             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14314                     _(cps->which), cps->program);
14315         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14316                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14317                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14318                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14319                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14320                 } else {
14321                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14322                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14323                     gameInfo.result = res;
14324                 }
14325                 gameInfo.resultDetails = StrSave(buf);
14326             }
14327             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14328             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14329         } else {
14330             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14331                     _(cps->which), cps->program);
14332             RemoveInputSource(cps->isr);
14333
14334             /* [AS] Program is misbehaving badly... kill it */
14335             if( count == -2 ) {
14336                 DestroyChildProcess( cps->pr, 9 );
14337                 cps->pr = NoProc;
14338             }
14339
14340             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14341         }
14342         return;
14343     }
14344
14345     if ((end_str = strchr(message, '\r')) != NULL)
14346       *end_str = NULLCHAR;
14347     if ((end_str = strchr(message, '\n')) != NULL)
14348       *end_str = NULLCHAR;
14349
14350     if (appData.debugMode) {
14351         TimeMark now; int print = 1;
14352         char *quote = ""; char c; int i;
14353
14354         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14355                 char start = message[0];
14356                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14357                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14358                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14359                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14360                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14361                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14362                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14363                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14364                    sscanf(message, "hint: %c", &c)!=1 && 
14365                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14366                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14367                     print = (appData.engineComments >= 2);
14368                 }
14369                 message[0] = start; // restore original message
14370         }
14371         if(print) {
14372                 GetTimeMark(&now);
14373                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14374                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14375                         quote,
14376                         message);
14377         }
14378     }
14379
14380     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14381     if (appData.icsEngineAnalyze) {
14382         if (strstr(message, "whisper") != NULL ||
14383              strstr(message, "kibitz") != NULL ||
14384             strstr(message, "tellics") != NULL) return;
14385     }
14386
14387     HandleMachineMove(message, cps);
14388 }
14389
14390
14391 void
14392 SendTimeControl(cps, mps, tc, inc, sd, st)
14393      ChessProgramState *cps;
14394      int mps, inc, sd, st;
14395      long tc;
14396 {
14397     char buf[MSG_SIZ];
14398     int seconds;
14399
14400     if( timeControl_2 > 0 ) {
14401         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14402             tc = timeControl_2;
14403         }
14404     }
14405     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14406     inc /= cps->timeOdds;
14407     st  /= cps->timeOdds;
14408
14409     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14410
14411     if (st > 0) {
14412       /* Set exact time per move, normally using st command */
14413       if (cps->stKludge) {
14414         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14415         seconds = st % 60;
14416         if (seconds == 0) {
14417           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14418         } else {
14419           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14420         }
14421       } else {
14422         snprintf(buf, MSG_SIZ, "st %d\n", st);
14423       }
14424     } else {
14425       /* Set conventional or incremental time control, using level command */
14426       if (seconds == 0) {
14427         /* Note old gnuchess bug -- minutes:seconds used to not work.
14428            Fixed in later versions, but still avoid :seconds
14429            when seconds is 0. */
14430         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14431       } else {
14432         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14433                  seconds, inc/1000.);
14434       }
14435     }
14436     SendToProgram(buf, cps);
14437
14438     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14439     /* Orthogonally, limit search to given depth */
14440     if (sd > 0) {
14441       if (cps->sdKludge) {
14442         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14443       } else {
14444         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14445       }
14446       SendToProgram(buf, cps);
14447     }
14448
14449     if(cps->nps >= 0) { /* [HGM] nps */
14450         if(cps->supportsNPS == FALSE)
14451           cps->nps = -1; // don't use if engine explicitly says not supported!
14452         else {
14453           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14454           SendToProgram(buf, cps);
14455         }
14456     }
14457 }
14458
14459 ChessProgramState *WhitePlayer()
14460 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14461 {
14462     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14463        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14464         return &second;
14465     return &first;
14466 }
14467
14468 void
14469 SendTimeRemaining(cps, machineWhite)
14470      ChessProgramState *cps;
14471      int /*boolean*/ machineWhite;
14472 {
14473     char message[MSG_SIZ];
14474     long time, otime;
14475
14476     /* Note: this routine must be called when the clocks are stopped
14477        or when they have *just* been set or switched; otherwise
14478        it will be off by the time since the current tick started.
14479     */
14480     if (machineWhite) {
14481         time = whiteTimeRemaining / 10;
14482         otime = blackTimeRemaining / 10;
14483     } else {
14484         time = blackTimeRemaining / 10;
14485         otime = whiteTimeRemaining / 10;
14486     }
14487     /* [HGM] translate opponent's time by time-odds factor */
14488     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14489     if (appData.debugMode) {
14490         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14491     }
14492
14493     if (time <= 0) time = 1;
14494     if (otime <= 0) otime = 1;
14495
14496     snprintf(message, MSG_SIZ, "time %ld\n", time);
14497     SendToProgram(message, cps);
14498
14499     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14500     SendToProgram(message, cps);
14501 }
14502
14503 int
14504 BoolFeature(p, name, loc, cps)
14505      char **p;
14506      char *name;
14507      int *loc;
14508      ChessProgramState *cps;
14509 {
14510   char buf[MSG_SIZ];
14511   int len = strlen(name);
14512   int val;
14513
14514   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14515     (*p) += len + 1;
14516     sscanf(*p, "%d", &val);
14517     *loc = (val != 0);
14518     while (**p && **p != ' ')
14519       (*p)++;
14520     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14521     SendToProgram(buf, cps);
14522     return TRUE;
14523   }
14524   return FALSE;
14525 }
14526
14527 int
14528 IntFeature(p, name, loc, cps)
14529      char **p;
14530      char *name;
14531      int *loc;
14532      ChessProgramState *cps;
14533 {
14534   char buf[MSG_SIZ];
14535   int len = strlen(name);
14536   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14537     (*p) += len + 1;
14538     sscanf(*p, "%d", loc);
14539     while (**p && **p != ' ') (*p)++;
14540     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14541     SendToProgram(buf, cps);
14542     return TRUE;
14543   }
14544   return FALSE;
14545 }
14546
14547 int
14548 StringFeature(p, name, loc, cps)
14549      char **p;
14550      char *name;
14551      char loc[];
14552      ChessProgramState *cps;
14553 {
14554   char buf[MSG_SIZ];
14555   int len = strlen(name);
14556   if (strncmp((*p), name, len) == 0
14557       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14558     (*p) += len + 2;
14559     sscanf(*p, "%[^\"]", loc);
14560     while (**p && **p != '\"') (*p)++;
14561     if (**p == '\"') (*p)++;
14562     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14563     SendToProgram(buf, cps);
14564     return TRUE;
14565   }
14566   return FALSE;
14567 }
14568
14569 int
14570 ParseOption(Option *opt, ChessProgramState *cps)
14571 // [HGM] options: process the string that defines an engine option, and determine
14572 // name, type, default value, and allowed value range
14573 {
14574         char *p, *q, buf[MSG_SIZ];
14575         int n, min = (-1)<<31, max = 1<<31, def;
14576
14577         if(p = strstr(opt->name, " -spin ")) {
14578             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14579             if(max < min) max = min; // enforce consistency
14580             if(def < min) def = min;
14581             if(def > max) def = max;
14582             opt->value = def;
14583             opt->min = min;
14584             opt->max = max;
14585             opt->type = Spin;
14586         } else if((p = strstr(opt->name, " -slider "))) {
14587             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14588             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14589             if(max < min) max = min; // enforce consistency
14590             if(def < min) def = min;
14591             if(def > max) def = max;
14592             opt->value = def;
14593             opt->min = min;
14594             opt->max = max;
14595             opt->type = Spin; // Slider;
14596         } else if((p = strstr(opt->name, " -string "))) {
14597             opt->textValue = p+9;
14598             opt->type = TextBox;
14599         } else if((p = strstr(opt->name, " -file "))) {
14600             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14601             opt->textValue = p+7;
14602             opt->type = FileName; // FileName;
14603         } else if((p = strstr(opt->name, " -path "))) {
14604             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14605             opt->textValue = p+7;
14606             opt->type = PathName; // PathName;
14607         } else if(p = strstr(opt->name, " -check ")) {
14608             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14609             opt->value = (def != 0);
14610             opt->type = CheckBox;
14611         } else if(p = strstr(opt->name, " -combo ")) {
14612             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14613             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14614             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14615             opt->value = n = 0;
14616             while(q = StrStr(q, " /// ")) {
14617                 n++; *q = 0;    // count choices, and null-terminate each of them
14618                 q += 5;
14619                 if(*q == '*') { // remember default, which is marked with * prefix
14620                     q++;
14621                     opt->value = n;
14622                 }
14623                 cps->comboList[cps->comboCnt++] = q;
14624             }
14625             cps->comboList[cps->comboCnt++] = NULL;
14626             opt->max = n + 1;
14627             opt->type = ComboBox;
14628         } else if(p = strstr(opt->name, " -button")) {
14629             opt->type = Button;
14630         } else if(p = strstr(opt->name, " -save")) {
14631             opt->type = SaveButton;
14632         } else return FALSE;
14633         *p = 0; // terminate option name
14634         // now look if the command-line options define a setting for this engine option.
14635         if(cps->optionSettings && cps->optionSettings[0])
14636             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14637         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14638           snprintf(buf, MSG_SIZ, "option %s", p);
14639                 if(p = strstr(buf, ",")) *p = 0;
14640                 if(q = strchr(buf, '=')) switch(opt->type) {
14641                     case ComboBox:
14642                         for(n=0; n<opt->max; n++)
14643                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14644                         break;
14645                     case TextBox:
14646                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14647                         break;
14648                     case Spin:
14649                     case CheckBox:
14650                         opt->value = atoi(q+1);
14651                     default:
14652                         break;
14653                 }
14654                 strcat(buf, "\n");
14655                 SendToProgram(buf, cps);
14656         }
14657         return TRUE;
14658 }
14659
14660 void
14661 FeatureDone(cps, val)
14662      ChessProgramState* cps;
14663      int val;
14664 {
14665   DelayedEventCallback cb = GetDelayedEvent();
14666   if ((cb == InitBackEnd3 && cps == &first) ||
14667       (cb == SettingsMenuIfReady && cps == &second) ||
14668       (cb == LoadEngine) ||
14669       (cb == TwoMachinesEventIfReady)) {
14670     CancelDelayedEvent();
14671     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14672   }
14673   cps->initDone = val;
14674 }
14675
14676 /* Parse feature command from engine */
14677 void
14678 ParseFeatures(args, cps)
14679      char* args;
14680      ChessProgramState *cps;
14681 {
14682   char *p = args;
14683   char *q;
14684   int val;
14685   char buf[MSG_SIZ];
14686
14687   for (;;) {
14688     while (*p == ' ') p++;
14689     if (*p == NULLCHAR) return;
14690
14691     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14692     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14693     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14694     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14695     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14696     if (BoolFeature(&p, "reuse", &val, cps)) {
14697       /* Engine can disable reuse, but can't enable it if user said no */
14698       if (!val) cps->reuse = FALSE;
14699       continue;
14700     }
14701     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14702     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14703       if (gameMode == TwoMachinesPlay) {
14704         DisplayTwoMachinesTitle();
14705       } else {
14706         DisplayTitle("");
14707       }
14708       continue;
14709     }
14710     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14711     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14712     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14713     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14714     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14715     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14716     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14717     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14718     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14719     if (IntFeature(&p, "done", &val, cps)) {
14720       FeatureDone(cps, val);
14721       continue;
14722     }
14723     /* Added by Tord: */
14724     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14725     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14726     /* End of additions by Tord */
14727
14728     /* [HGM] added features: */
14729     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14730     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14731     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14732     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14733     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14734     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14735     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14736         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14737           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14738             SendToProgram(buf, cps);
14739             continue;
14740         }
14741         if(cps->nrOptions >= MAX_OPTIONS) {
14742             cps->nrOptions--;
14743             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14744             DisplayError(buf, 0);
14745         }
14746         continue;
14747     }
14748     /* End of additions by HGM */
14749
14750     /* unknown feature: complain and skip */
14751     q = p;
14752     while (*q && *q != '=') q++;
14753     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14754     SendToProgram(buf, cps);
14755     p = q;
14756     if (*p == '=') {
14757       p++;
14758       if (*p == '\"') {
14759         p++;
14760         while (*p && *p != '\"') p++;
14761         if (*p == '\"') p++;
14762       } else {
14763         while (*p && *p != ' ') p++;
14764       }
14765     }
14766   }
14767
14768 }
14769
14770 void
14771 PeriodicUpdatesEvent(newState)
14772      int newState;
14773 {
14774     if (newState == appData.periodicUpdates)
14775       return;
14776
14777     appData.periodicUpdates=newState;
14778
14779     /* Display type changes, so update it now */
14780 //    DisplayAnalysis();
14781
14782     /* Get the ball rolling again... */
14783     if (newState) {
14784         AnalysisPeriodicEvent(1);
14785         StartAnalysisClock();
14786     }
14787 }
14788
14789 void
14790 PonderNextMoveEvent(newState)
14791      int newState;
14792 {
14793     if (newState == appData.ponderNextMove) return;
14794     if (gameMode == EditPosition) EditPositionDone(TRUE);
14795     if (newState) {
14796         SendToProgram("hard\n", &first);
14797         if (gameMode == TwoMachinesPlay) {
14798             SendToProgram("hard\n", &second);
14799         }
14800     } else {
14801         SendToProgram("easy\n", &first);
14802         thinkOutput[0] = NULLCHAR;
14803         if (gameMode == TwoMachinesPlay) {
14804             SendToProgram("easy\n", &second);
14805         }
14806     }
14807     appData.ponderNextMove = newState;
14808 }
14809
14810 void
14811 NewSettingEvent(option, feature, command, value)
14812      char *command;
14813      int option, value, *feature;
14814 {
14815     char buf[MSG_SIZ];
14816
14817     if (gameMode == EditPosition) EditPositionDone(TRUE);
14818     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14819     if(feature == NULL || *feature) SendToProgram(buf, &first);
14820     if (gameMode == TwoMachinesPlay) {
14821         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14822     }
14823 }
14824
14825 void
14826 ShowThinkingEvent()
14827 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14828 {
14829     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14830     int newState = appData.showThinking
14831         // [HGM] thinking: other features now need thinking output as well
14832         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14833
14834     if (oldState == newState) return;
14835     oldState = newState;
14836     if (gameMode == EditPosition) EditPositionDone(TRUE);
14837     if (oldState) {
14838         SendToProgram("post\n", &first);
14839         if (gameMode == TwoMachinesPlay) {
14840             SendToProgram("post\n", &second);
14841         }
14842     } else {
14843         SendToProgram("nopost\n", &first);
14844         thinkOutput[0] = NULLCHAR;
14845         if (gameMode == TwoMachinesPlay) {
14846             SendToProgram("nopost\n", &second);
14847         }
14848     }
14849 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14850 }
14851
14852 void
14853 AskQuestionEvent(title, question, replyPrefix, which)
14854      char *title; char *question; char *replyPrefix; char *which;
14855 {
14856   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14857   if (pr == NoProc) return;
14858   AskQuestion(title, question, replyPrefix, pr);
14859 }
14860
14861 void
14862 TypeInEvent(char firstChar)
14863 {
14864     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14865         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14866         gameMode == AnalyzeMode || gameMode == EditGame || \r
14867         gameMode == EditPosition || gameMode == IcsExamining ||\r
14868         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14869         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14870                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14871                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14872         gameMode == Training) PopUpMoveDialog(firstChar);
14873 }
14874
14875 void
14876 TypeInDoneEvent(char *move)
14877 {
14878         Board board;
14879         int n, fromX, fromY, toX, toY;
14880         char promoChar;
14881         ChessMove moveType;\r
14882
14883         // [HGM] FENedit\r
14884         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14885                 EditPositionPasteFEN(move);\r
14886                 return;\r
14887         }\r
14888         // [HGM] movenum: allow move number to be typed in any mode\r
14889         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14890           ToNrEvent(2*n-1);\r
14891           return;\r
14892         }\r
14893
14894       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14895         gameMode != Training) {\r
14896         DisplayMoveError(_("Displayed move is not current"));\r
14897       } else {\r
14898         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14899           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14900         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14901         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14902           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14903           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14904         } else {\r
14905           DisplayMoveError(_("Could not parse move"));\r
14906         }
14907       }\r
14908 }\r
14909
14910 void
14911 DisplayMove(moveNumber)
14912      int moveNumber;
14913 {
14914     char message[MSG_SIZ];
14915     char res[MSG_SIZ];
14916     char cpThinkOutput[MSG_SIZ];
14917
14918     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14919
14920     if (moveNumber == forwardMostMove - 1 ||
14921         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14922
14923         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14924
14925         if (strchr(cpThinkOutput, '\n')) {
14926             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14927         }
14928     } else {
14929         *cpThinkOutput = NULLCHAR;
14930     }
14931
14932     /* [AS] Hide thinking from human user */
14933     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14934         *cpThinkOutput = NULLCHAR;
14935         if( thinkOutput[0] != NULLCHAR ) {
14936             int i;
14937
14938             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14939                 cpThinkOutput[i] = '.';
14940             }
14941             cpThinkOutput[i] = NULLCHAR;
14942             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14943         }
14944     }
14945
14946     if (moveNumber == forwardMostMove - 1 &&
14947         gameInfo.resultDetails != NULL) {
14948         if (gameInfo.resultDetails[0] == NULLCHAR) {
14949           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14950         } else {
14951           snprintf(res, MSG_SIZ, " {%s} %s",
14952                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14953         }
14954     } else {
14955         res[0] = NULLCHAR;
14956     }
14957
14958     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14959         DisplayMessage(res, cpThinkOutput);
14960     } else {
14961       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14962                 WhiteOnMove(moveNumber) ? " " : ".. ",
14963                 parseList[moveNumber], res);
14964         DisplayMessage(message, cpThinkOutput);
14965     }
14966 }
14967
14968 void
14969 DisplayComment(moveNumber, text)
14970      int moveNumber;
14971      char *text;
14972 {
14973     char title[MSG_SIZ];
14974     char buf[8000]; // comment can be long!
14975     int score, depth;
14976
14977     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14978       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14979     } else {
14980       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14981               WhiteOnMove(moveNumber) ? " " : ".. ",
14982               parseList[moveNumber]);
14983     }
14984     // [HGM] PV info: display PV info together with (or as) comment
14985     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14986       if(text == NULL) text = "";
14987       score = pvInfoList[moveNumber].score;
14988       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14989               depth, (pvInfoList[moveNumber].time+50)/100, text);
14990       text = buf;
14991     }
14992     if (text != NULL && (appData.autoDisplayComment || commentUp))
14993         CommentPopUp(title, text);
14994 }
14995
14996 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14997  * might be busy thinking or pondering.  It can be omitted if your
14998  * gnuchess is configured to stop thinking immediately on any user
14999  * input.  However, that gnuchess feature depends on the FIONREAD
15000  * ioctl, which does not work properly on some flavors of Unix.
15001  */
15002 void
15003 Attention(cps)
15004      ChessProgramState *cps;
15005 {
15006 #if ATTENTION
15007     if (!cps->useSigint) return;
15008     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15009     switch (gameMode) {
15010       case MachinePlaysWhite:
15011       case MachinePlaysBlack:
15012       case TwoMachinesPlay:
15013       case IcsPlayingWhite:
15014       case IcsPlayingBlack:
15015       case AnalyzeMode:
15016       case AnalyzeFile:
15017         /* Skip if we know it isn't thinking */
15018         if (!cps->maybeThinking) return;
15019         if (appData.debugMode)
15020           fprintf(debugFP, "Interrupting %s\n", cps->which);
15021         InterruptChildProcess(cps->pr);
15022         cps->maybeThinking = FALSE;
15023         break;
15024       default:
15025         break;
15026     }
15027 #endif /*ATTENTION*/
15028 }
15029
15030 int
15031 CheckFlags()
15032 {
15033     if (whiteTimeRemaining <= 0) {
15034         if (!whiteFlag) {
15035             whiteFlag = TRUE;
15036             if (appData.icsActive) {
15037                 if (appData.autoCallFlag &&
15038                     gameMode == IcsPlayingBlack && !blackFlag) {
15039                   SendToICS(ics_prefix);
15040                   SendToICS("flag\n");
15041                 }
15042             } else {
15043                 if (blackFlag) {
15044                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15045                 } else {
15046                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15047                     if (appData.autoCallFlag) {
15048                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15049                         return TRUE;
15050                     }
15051                 }
15052             }
15053         }
15054     }
15055     if (blackTimeRemaining <= 0) {
15056         if (!blackFlag) {
15057             blackFlag = TRUE;
15058             if (appData.icsActive) {
15059                 if (appData.autoCallFlag &&
15060                     gameMode == IcsPlayingWhite && !whiteFlag) {
15061                   SendToICS(ics_prefix);
15062                   SendToICS("flag\n");
15063                 }
15064             } else {
15065                 if (whiteFlag) {
15066                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15067                 } else {
15068                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15069                     if (appData.autoCallFlag) {
15070                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15071                         return TRUE;
15072                     }
15073                 }
15074             }
15075         }
15076     }
15077     return FALSE;
15078 }
15079
15080 void
15081 CheckTimeControl()
15082 {
15083     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15084         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15085
15086     /*
15087      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15088      */
15089     if ( !WhiteOnMove(forwardMostMove) ) {
15090         /* White made time control */
15091         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15092         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15093         /* [HGM] time odds: correct new time quota for time odds! */
15094                                             / WhitePlayer()->timeOdds;
15095         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15096     } else {
15097         lastBlack -= blackTimeRemaining;
15098         /* Black made time control */
15099         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15100                                             / WhitePlayer()->other->timeOdds;
15101         lastWhite = whiteTimeRemaining;
15102     }
15103 }
15104
15105 void
15106 DisplayBothClocks()
15107 {
15108     int wom = gameMode == EditPosition ?
15109       !blackPlaysFirst : WhiteOnMove(currentMove);
15110     DisplayWhiteClock(whiteTimeRemaining, wom);
15111     DisplayBlackClock(blackTimeRemaining, !wom);
15112 }
15113
15114
15115 /* Timekeeping seems to be a portability nightmare.  I think everyone
15116    has ftime(), but I'm really not sure, so I'm including some ifdefs
15117    to use other calls if you don't.  Clocks will be less accurate if
15118    you have neither ftime nor gettimeofday.
15119 */
15120
15121 /* VS 2008 requires the #include outside of the function */
15122 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15123 #include <sys/timeb.h>
15124 #endif
15125
15126 /* Get the current time as a TimeMark */
15127 void
15128 GetTimeMark(tm)
15129      TimeMark *tm;
15130 {
15131 #if HAVE_GETTIMEOFDAY
15132
15133     struct timeval timeVal;
15134     struct timezone timeZone;
15135
15136     gettimeofday(&timeVal, &timeZone);
15137     tm->sec = (long) timeVal.tv_sec;
15138     tm->ms = (int) (timeVal.tv_usec / 1000L);
15139
15140 #else /*!HAVE_GETTIMEOFDAY*/
15141 #if HAVE_FTIME
15142
15143 // include <sys/timeb.h> / moved to just above start of function
15144     struct timeb timeB;
15145
15146     ftime(&timeB);
15147     tm->sec = (long) timeB.time;
15148     tm->ms = (int) timeB.millitm;
15149
15150 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15151     tm->sec = (long) time(NULL);
15152     tm->ms = 0;
15153 #endif
15154 #endif
15155 }
15156
15157 /* Return the difference in milliseconds between two
15158    time marks.  We assume the difference will fit in a long!
15159 */
15160 long
15161 SubtractTimeMarks(tm2, tm1)
15162      TimeMark *tm2, *tm1;
15163 {
15164     return 1000L*(tm2->sec - tm1->sec) +
15165            (long) (tm2->ms - tm1->ms);
15166 }
15167
15168
15169 /*
15170  * Code to manage the game clocks.
15171  *
15172  * In tournament play, black starts the clock and then white makes a move.
15173  * We give the human user a slight advantage if he is playing white---the
15174  * clocks don't run until he makes his first move, so it takes zero time.
15175  * Also, we don't account for network lag, so we could get out of sync
15176  * with GNU Chess's clock -- but then, referees are always right.
15177  */
15178
15179 static TimeMark tickStartTM;
15180 static long intendedTickLength;
15181
15182 long
15183 NextTickLength(timeRemaining)
15184      long timeRemaining;
15185 {
15186     long nominalTickLength, nextTickLength;
15187
15188     if (timeRemaining > 0L && timeRemaining <= 10000L)
15189       nominalTickLength = 100L;
15190     else
15191       nominalTickLength = 1000L;
15192     nextTickLength = timeRemaining % nominalTickLength;
15193     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15194
15195     return nextTickLength;
15196 }
15197
15198 /* Adjust clock one minute up or down */
15199 void
15200 AdjustClock(Boolean which, int dir)
15201 {
15202     if(which) blackTimeRemaining += 60000*dir;
15203     else      whiteTimeRemaining += 60000*dir;
15204     DisplayBothClocks();
15205 }
15206
15207 /* Stop clocks and reset to a fresh time control */
15208 void
15209 ResetClocks()
15210 {
15211     (void) StopClockTimer();
15212     if (appData.icsActive) {
15213         whiteTimeRemaining = blackTimeRemaining = 0;
15214     } else if (searchTime) {
15215         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15216         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15217     } else { /* [HGM] correct new time quote for time odds */
15218         whiteTC = blackTC = fullTimeControlString;
15219         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15220         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15221     }
15222     if (whiteFlag || blackFlag) {
15223         DisplayTitle("");
15224         whiteFlag = blackFlag = FALSE;
15225     }
15226     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15227     DisplayBothClocks();
15228 }
15229
15230 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15231
15232 /* Decrement running clock by amount of time that has passed */
15233 void
15234 DecrementClocks()
15235 {
15236     long timeRemaining;
15237     long lastTickLength, fudge;
15238     TimeMark now;
15239
15240     if (!appData.clockMode) return;
15241     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15242
15243     GetTimeMark(&now);
15244
15245     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15246
15247     /* Fudge if we woke up a little too soon */
15248     fudge = intendedTickLength - lastTickLength;
15249     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15250
15251     if (WhiteOnMove(forwardMostMove)) {
15252         if(whiteNPS >= 0) lastTickLength = 0;
15253         timeRemaining = whiteTimeRemaining -= lastTickLength;
15254         if(timeRemaining < 0 && !appData.icsActive) {
15255             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15256             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15257                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15258                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15259             }
15260         }
15261         DisplayWhiteClock(whiteTimeRemaining - fudge,
15262                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15263     } else {
15264         if(blackNPS >= 0) lastTickLength = 0;
15265         timeRemaining = blackTimeRemaining -= lastTickLength;
15266         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15267             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15268             if(suddenDeath) {
15269                 blackStartMove = forwardMostMove;
15270                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15271             }
15272         }
15273         DisplayBlackClock(blackTimeRemaining - fudge,
15274                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15275     }
15276     if (CheckFlags()) return;
15277
15278     tickStartTM = now;
15279     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15280     StartClockTimer(intendedTickLength);
15281
15282     /* if the time remaining has fallen below the alarm threshold, sound the
15283      * alarm. if the alarm has sounded and (due to a takeback or time control
15284      * with increment) the time remaining has increased to a level above the
15285      * threshold, reset the alarm so it can sound again.
15286      */
15287
15288     if (appData.icsActive && appData.icsAlarm) {
15289
15290         /* make sure we are dealing with the user's clock */
15291         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15292                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15293            )) return;
15294
15295         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15296             alarmSounded = FALSE;
15297         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15298             PlayAlarmSound();
15299             alarmSounded = TRUE;
15300         }
15301     }
15302 }
15303
15304
15305 /* A player has just moved, so stop the previously running
15306    clock and (if in clock mode) start the other one.
15307    We redisplay both clocks in case we're in ICS mode, because
15308    ICS gives us an update to both clocks after every move.
15309    Note that this routine is called *after* forwardMostMove
15310    is updated, so the last fractional tick must be subtracted
15311    from the color that is *not* on move now.
15312 */
15313 void
15314 SwitchClocks(int newMoveNr)
15315 {
15316     long lastTickLength;
15317     TimeMark now;
15318     int flagged = FALSE;
15319
15320     GetTimeMark(&now);
15321
15322     if (StopClockTimer() && appData.clockMode) {
15323         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15324         if (!WhiteOnMove(forwardMostMove)) {
15325             if(blackNPS >= 0) lastTickLength = 0;
15326             blackTimeRemaining -= lastTickLength;
15327            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15328 //         if(pvInfoList[forwardMostMove].time == -1)
15329                  pvInfoList[forwardMostMove].time =               // use GUI time
15330                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15331         } else {
15332            if(whiteNPS >= 0) lastTickLength = 0;
15333            whiteTimeRemaining -= lastTickLength;
15334            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15335 //         if(pvInfoList[forwardMostMove].time == -1)
15336                  pvInfoList[forwardMostMove].time =
15337                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15338         }
15339         flagged = CheckFlags();
15340     }
15341     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15342     CheckTimeControl();
15343
15344     if (flagged || !appData.clockMode) return;
15345
15346     switch (gameMode) {
15347       case MachinePlaysBlack:
15348       case MachinePlaysWhite:
15349       case BeginningOfGame:
15350         if (pausing) return;
15351         break;
15352
15353       case EditGame:
15354       case PlayFromGameFile:
15355       case IcsExamining:
15356         return;
15357
15358       default:
15359         break;
15360     }
15361
15362     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15363         if(WhiteOnMove(forwardMostMove))
15364              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15365         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15366     }
15367
15368     tickStartTM = now;
15369     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15370       whiteTimeRemaining : blackTimeRemaining);
15371     StartClockTimer(intendedTickLength);
15372 }
15373
15374
15375 /* Stop both clocks */
15376 void
15377 StopClocks()
15378 {
15379     long lastTickLength;
15380     TimeMark now;
15381
15382     if (!StopClockTimer()) return;
15383     if (!appData.clockMode) return;
15384
15385     GetTimeMark(&now);
15386
15387     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15388     if (WhiteOnMove(forwardMostMove)) {
15389         if(whiteNPS >= 0) lastTickLength = 0;
15390         whiteTimeRemaining -= lastTickLength;
15391         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15392     } else {
15393         if(blackNPS >= 0) lastTickLength = 0;
15394         blackTimeRemaining -= lastTickLength;
15395         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15396     }
15397     CheckFlags();
15398 }
15399
15400 /* Start clock of player on move.  Time may have been reset, so
15401    if clock is already running, stop and restart it. */
15402 void
15403 StartClocks()
15404 {
15405     (void) StopClockTimer(); /* in case it was running already */
15406     DisplayBothClocks();
15407     if (CheckFlags()) return;
15408
15409     if (!appData.clockMode) return;
15410     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15411
15412     GetTimeMark(&tickStartTM);
15413     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15414       whiteTimeRemaining : blackTimeRemaining);
15415
15416    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15417     whiteNPS = blackNPS = -1;
15418     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15419        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15420         whiteNPS = first.nps;
15421     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15422        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15423         blackNPS = first.nps;
15424     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15425         whiteNPS = second.nps;
15426     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15427         blackNPS = second.nps;
15428     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15429
15430     StartClockTimer(intendedTickLength);
15431 }
15432
15433 char *
15434 TimeString(ms)
15435      long ms;
15436 {
15437     long second, minute, hour, day;
15438     char *sign = "";
15439     static char buf[32];
15440
15441     if (ms > 0 && ms <= 9900) {
15442       /* convert milliseconds to tenths, rounding up */
15443       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15444
15445       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15446       return buf;
15447     }
15448
15449     /* convert milliseconds to seconds, rounding up */
15450     /* use floating point to avoid strangeness of integer division
15451        with negative dividends on many machines */
15452     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15453
15454     if (second < 0) {
15455         sign = "-";
15456         second = -second;
15457     }
15458
15459     day = second / (60 * 60 * 24);
15460     second = second % (60 * 60 * 24);
15461     hour = second / (60 * 60);
15462     second = second % (60 * 60);
15463     minute = second / 60;
15464     second = second % 60;
15465
15466     if (day > 0)
15467       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15468               sign, day, hour, minute, second);
15469     else if (hour > 0)
15470       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15471     else
15472       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15473
15474     return buf;
15475 }
15476
15477
15478 /*
15479  * This is necessary because some C libraries aren't ANSI C compliant yet.
15480  */
15481 char *
15482 StrStr(string, match)
15483      char *string, *match;
15484 {
15485     int i, length;
15486
15487     length = strlen(match);
15488
15489     for (i = strlen(string) - length; i >= 0; i--, string++)
15490       if (!strncmp(match, string, length))
15491         return string;
15492
15493     return NULL;
15494 }
15495
15496 char *
15497 StrCaseStr(string, match)
15498      char *string, *match;
15499 {
15500     int i, j, length;
15501
15502     length = strlen(match);
15503
15504     for (i = strlen(string) - length; i >= 0; i--, string++) {
15505         for (j = 0; j < length; j++) {
15506             if (ToLower(match[j]) != ToLower(string[j]))
15507               break;
15508         }
15509         if (j == length) return string;
15510     }
15511
15512     return NULL;
15513 }
15514
15515 #ifndef _amigados
15516 int
15517 StrCaseCmp(s1, s2)
15518      char *s1, *s2;
15519 {
15520     char c1, c2;
15521
15522     for (;;) {
15523         c1 = ToLower(*s1++);
15524         c2 = ToLower(*s2++);
15525         if (c1 > c2) return 1;
15526         if (c1 < c2) return -1;
15527         if (c1 == NULLCHAR) return 0;
15528     }
15529 }
15530
15531
15532 int
15533 ToLower(c)
15534      int c;
15535 {
15536     return isupper(c) ? tolower(c) : c;
15537 }
15538
15539
15540 int
15541 ToUpper(c)
15542      int c;
15543 {
15544     return islower(c) ? toupper(c) : c;
15545 }
15546 #endif /* !_amigados    */
15547
15548 char *
15549 StrSave(s)
15550      char *s;
15551 {
15552   char *ret;
15553
15554   if ((ret = (char *) malloc(strlen(s) + 1)))
15555     {
15556       safeStrCpy(ret, s, strlen(s)+1);
15557     }
15558   return ret;
15559 }
15560
15561 char *
15562 StrSavePtr(s, savePtr)
15563      char *s, **savePtr;
15564 {
15565     if (*savePtr) {
15566         free(*savePtr);
15567     }
15568     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15569       safeStrCpy(*savePtr, s, strlen(s)+1);
15570     }
15571     return(*savePtr);
15572 }
15573
15574 char *
15575 PGNDate()
15576 {
15577     time_t clock;
15578     struct tm *tm;
15579     char buf[MSG_SIZ];
15580
15581     clock = time((time_t *)NULL);
15582     tm = localtime(&clock);
15583     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15584             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15585     return StrSave(buf);
15586 }
15587
15588
15589 char *
15590 PositionToFEN(move, overrideCastling)
15591      int move;
15592      char *overrideCastling;
15593 {
15594     int i, j, fromX, fromY, toX, toY;
15595     int whiteToPlay;
15596     char buf[128];
15597     char *p, *q;
15598     int emptycount;
15599     ChessSquare piece;
15600
15601     whiteToPlay = (gameMode == EditPosition) ?
15602       !blackPlaysFirst : (move % 2 == 0);
15603     p = buf;
15604
15605     /* Piece placement data */
15606     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15607         emptycount = 0;
15608         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15609             if (boards[move][i][j] == EmptySquare) {
15610                 emptycount++;
15611             } else { ChessSquare piece = boards[move][i][j];
15612                 if (emptycount > 0) {
15613                     if(emptycount<10) /* [HGM] can be >= 10 */
15614                         *p++ = '0' + emptycount;
15615                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15616                     emptycount = 0;
15617                 }
15618                 if(PieceToChar(piece) == '+') {
15619                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15620                     *p++ = '+';
15621                     piece = (ChessSquare)(DEMOTED piece);
15622                 }
15623                 *p++ = PieceToChar(piece);
15624                 if(p[-1] == '~') {
15625                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15626                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15627                     *p++ = '~';
15628                 }
15629             }
15630         }
15631         if (emptycount > 0) {
15632             if(emptycount<10) /* [HGM] can be >= 10 */
15633                 *p++ = '0' + emptycount;
15634             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15635             emptycount = 0;
15636         }
15637         *p++ = '/';
15638     }
15639     *(p - 1) = ' ';
15640
15641     /* [HGM] print Crazyhouse or Shogi holdings */
15642     if( gameInfo.holdingsWidth ) {
15643         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15644         q = p;
15645         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15646             piece = boards[move][i][BOARD_WIDTH-1];
15647             if( piece != EmptySquare )
15648               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15649                   *p++ = PieceToChar(piece);
15650         }
15651         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15652             piece = boards[move][BOARD_HEIGHT-i-1][0];
15653             if( piece != EmptySquare )
15654               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15655                   *p++ = PieceToChar(piece);
15656         }
15657
15658         if( q == p ) *p++ = '-';
15659         *p++ = ']';
15660         *p++ = ' ';
15661     }
15662
15663     /* Active color */
15664     *p++ = whiteToPlay ? 'w' : 'b';
15665     *p++ = ' ';
15666
15667   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15668     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15669   } else {
15670   if(nrCastlingRights) {
15671      q = p;
15672      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15673        /* [HGM] write directly from rights */
15674            if(boards[move][CASTLING][2] != NoRights &&
15675               boards[move][CASTLING][0] != NoRights   )
15676                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15677            if(boards[move][CASTLING][2] != NoRights &&
15678               boards[move][CASTLING][1] != NoRights   )
15679                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15680            if(boards[move][CASTLING][5] != NoRights &&
15681               boards[move][CASTLING][3] != NoRights   )
15682                 *p++ = boards[move][CASTLING][3] + AAA;
15683            if(boards[move][CASTLING][5] != NoRights &&
15684               boards[move][CASTLING][4] != NoRights   )
15685                 *p++ = boards[move][CASTLING][4] + AAA;
15686      } else {
15687
15688         /* [HGM] write true castling rights */
15689         if( nrCastlingRights == 6 ) {
15690             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15691                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15692             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15693                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15694             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15695                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15696             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15697                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15698         }
15699      }
15700      if (q == p) *p++ = '-'; /* No castling rights */
15701      *p++ = ' ';
15702   }
15703
15704   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15705      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15706     /* En passant target square */
15707     if (move > backwardMostMove) {
15708         fromX = moveList[move - 1][0] - AAA;
15709         fromY = moveList[move - 1][1] - ONE;
15710         toX = moveList[move - 1][2] - AAA;
15711         toY = moveList[move - 1][3] - ONE;
15712         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15713             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15714             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15715             fromX == toX) {
15716             /* 2-square pawn move just happened */
15717             *p++ = toX + AAA;
15718             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15719         } else {
15720             *p++ = '-';
15721         }
15722     } else if(move == backwardMostMove) {
15723         // [HGM] perhaps we should always do it like this, and forget the above?
15724         if((signed char)boards[move][EP_STATUS] >= 0) {
15725             *p++ = boards[move][EP_STATUS] + AAA;
15726             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15727         } else {
15728             *p++ = '-';
15729         }
15730     } else {
15731         *p++ = '-';
15732     }
15733     *p++ = ' ';
15734   }
15735   }
15736
15737     /* [HGM] find reversible plies */
15738     {   int i = 0, j=move;
15739
15740         if (appData.debugMode) { int k;
15741             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15742             for(k=backwardMostMove; k<=forwardMostMove; k++)
15743                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15744
15745         }
15746
15747         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15748         if( j == backwardMostMove ) i += initialRulePlies;
15749         sprintf(p, "%d ", i);
15750         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15751     }
15752     /* Fullmove number */
15753     sprintf(p, "%d", (move / 2) + 1);
15754
15755     return StrSave(buf);
15756 }
15757
15758 Boolean
15759 ParseFEN(board, blackPlaysFirst, fen)
15760     Board board;
15761      int *blackPlaysFirst;
15762      char *fen;
15763 {
15764     int i, j;
15765     char *p, c;
15766     int emptycount;
15767     ChessSquare piece;
15768
15769     p = fen;
15770
15771     /* [HGM] by default clear Crazyhouse holdings, if present */
15772     if(gameInfo.holdingsWidth) {
15773        for(i=0; i<BOARD_HEIGHT; i++) {
15774            board[i][0]             = EmptySquare; /* black holdings */
15775            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15776            board[i][1]             = (ChessSquare) 0; /* black counts */
15777            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15778        }
15779     }
15780
15781     /* Piece placement data */
15782     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15783         j = 0;
15784         for (;;) {
15785             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15786                 if (*p == '/') p++;
15787                 emptycount = gameInfo.boardWidth - j;
15788                 while (emptycount--)
15789                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15790                 break;
15791 #if(BOARD_FILES >= 10)
15792             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15793                 p++; emptycount=10;
15794                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15795                 while (emptycount--)
15796                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15797 #endif
15798             } else if (isdigit(*p)) {
15799                 emptycount = *p++ - '0';
15800                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15801                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15802                 while (emptycount--)
15803                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15804             } else if (*p == '+' || isalpha(*p)) {
15805                 if (j >= gameInfo.boardWidth) return FALSE;
15806                 if(*p=='+') {
15807                     piece = CharToPiece(*++p);
15808                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15809                     piece = (ChessSquare) (PROMOTED piece ); p++;
15810                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15811                 } else piece = CharToPiece(*p++);
15812
15813                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15814                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15815                     piece = (ChessSquare) (PROMOTED piece);
15816                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15817                     p++;
15818                 }
15819                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15820             } else {
15821                 return FALSE;
15822             }
15823         }
15824     }
15825     while (*p == '/' || *p == ' ') p++;
15826
15827     /* [HGM] look for Crazyhouse holdings here */
15828     while(*p==' ') p++;
15829     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15830         if(*p == '[') p++;
15831         if(*p == '-' ) p++; /* empty holdings */ else {
15832             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15833             /* if we would allow FEN reading to set board size, we would   */
15834             /* have to add holdings and shift the board read so far here   */
15835             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15836                 p++;
15837                 if((int) piece >= (int) BlackPawn ) {
15838                     i = (int)piece - (int)BlackPawn;
15839                     i = PieceToNumber((ChessSquare)i);
15840                     if( i >= gameInfo.holdingsSize ) return FALSE;
15841                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15842                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15843                 } else {
15844                     i = (int)piece - (int)WhitePawn;
15845                     i = PieceToNumber((ChessSquare)i);
15846                     if( i >= gameInfo.holdingsSize ) return FALSE;
15847                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15848                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15849                 }
15850             }
15851         }
15852         if(*p == ']') p++;
15853     }
15854
15855     while(*p == ' ') p++;
15856
15857     /* Active color */
15858     c = *p++;
15859     if(appData.colorNickNames) {
15860       if( c == appData.colorNickNames[0] ) c = 'w'; else
15861       if( c == appData.colorNickNames[1] ) c = 'b';
15862     }
15863     switch (c) {
15864       case 'w':
15865         *blackPlaysFirst = FALSE;
15866         break;
15867       case 'b':
15868         *blackPlaysFirst = TRUE;
15869         break;
15870       default:
15871         return FALSE;
15872     }
15873
15874     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15875     /* return the extra info in global variiables             */
15876
15877     /* set defaults in case FEN is incomplete */
15878     board[EP_STATUS] = EP_UNKNOWN;
15879     for(i=0; i<nrCastlingRights; i++ ) {
15880         board[CASTLING][i] =
15881             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15882     }   /* assume possible unless obviously impossible */
15883     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15884     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15885     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15886                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15887     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15888     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15889     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15890                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15891     FENrulePlies = 0;
15892
15893     while(*p==' ') p++;
15894     if(nrCastlingRights) {
15895       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15896           /* castling indicator present, so default becomes no castlings */
15897           for(i=0; i<nrCastlingRights; i++ ) {
15898                  board[CASTLING][i] = NoRights;
15899           }
15900       }
15901       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15902              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15903              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15904              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15905         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15906
15907         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15908             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15909             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15910         }
15911         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15912             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15913         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15914                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15915         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15916                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15917         switch(c) {
15918           case'K':
15919               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15920               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15921               board[CASTLING][2] = whiteKingFile;
15922               break;
15923           case'Q':
15924               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15925               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15926               board[CASTLING][2] = whiteKingFile;
15927               break;
15928           case'k':
15929               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15930               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15931               board[CASTLING][5] = blackKingFile;
15932               break;
15933           case'q':
15934               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15935               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15936               board[CASTLING][5] = blackKingFile;
15937           case '-':
15938               break;
15939           default: /* FRC castlings */
15940               if(c >= 'a') { /* black rights */
15941                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15942                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15943                   if(i == BOARD_RGHT) break;
15944                   board[CASTLING][5] = i;
15945                   c -= AAA;
15946                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15947                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15948                   if(c > i)
15949                       board[CASTLING][3] = c;
15950                   else
15951                       board[CASTLING][4] = c;
15952               } else { /* white rights */
15953                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15954                     if(board[0][i] == WhiteKing) break;
15955                   if(i == BOARD_RGHT) break;
15956                   board[CASTLING][2] = i;
15957                   c -= AAA - 'a' + 'A';
15958                   if(board[0][c] >= WhiteKing) break;
15959                   if(c > i)
15960                       board[CASTLING][0] = c;
15961                   else
15962                       board[CASTLING][1] = c;
15963               }
15964         }
15965       }
15966       for(i=0; i<nrCastlingRights; i++)
15967         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15968     if (appData.debugMode) {
15969         fprintf(debugFP, "FEN castling rights:");
15970         for(i=0; i<nrCastlingRights; i++)
15971         fprintf(debugFP, " %d", board[CASTLING][i]);
15972         fprintf(debugFP, "\n");
15973     }
15974
15975       while(*p==' ') p++;
15976     }
15977
15978     /* read e.p. field in games that know e.p. capture */
15979     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15980        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15981       if(*p=='-') {
15982         p++; board[EP_STATUS] = EP_NONE;
15983       } else {
15984          char c = *p++ - AAA;
15985
15986          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15987          if(*p >= '0' && *p <='9') p++;
15988          board[EP_STATUS] = c;
15989       }
15990     }
15991
15992
15993     if(sscanf(p, "%d", &i) == 1) {
15994         FENrulePlies = i; /* 50-move ply counter */
15995         /* (The move number is still ignored)    */
15996     }
15997
15998     return TRUE;
15999 }
16000
16001 void
16002 EditPositionPasteFEN(char *fen)
16003 {
16004   if (fen != NULL) {
16005     Board initial_position;
16006
16007     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16008       DisplayError(_("Bad FEN position in clipboard"), 0);
16009       return ;
16010     } else {
16011       int savedBlackPlaysFirst = blackPlaysFirst;
16012       EditPositionEvent();
16013       blackPlaysFirst = savedBlackPlaysFirst;
16014       CopyBoard(boards[0], initial_position);
16015       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16016       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16017       DisplayBothClocks();
16018       DrawPosition(FALSE, boards[currentMove]);
16019     }
16020   }
16021 }
16022
16023 static char cseq[12] = "\\   ";
16024
16025 Boolean set_cont_sequence(char *new_seq)
16026 {
16027     int len;
16028     Boolean ret;
16029
16030     // handle bad attempts to set the sequence
16031         if (!new_seq)
16032                 return 0; // acceptable error - no debug
16033
16034     len = strlen(new_seq);
16035     ret = (len > 0) && (len < sizeof(cseq));
16036     if (ret)
16037       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16038     else if (appData.debugMode)
16039       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16040     return ret;
16041 }
16042
16043 /*
16044     reformat a source message so words don't cross the width boundary.  internal
16045     newlines are not removed.  returns the wrapped size (no null character unless
16046     included in source message).  If dest is NULL, only calculate the size required
16047     for the dest buffer.  lp argument indicats line position upon entry, and it's
16048     passed back upon exit.
16049 */
16050 int wrap(char *dest, char *src, int count, int width, int *lp)
16051 {
16052     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16053
16054     cseq_len = strlen(cseq);
16055     old_line = line = *lp;
16056     ansi = len = clen = 0;
16057
16058     for (i=0; i < count; i++)
16059     {
16060         if (src[i] == '\033')
16061             ansi = 1;
16062
16063         // if we hit the width, back up
16064         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16065         {
16066             // store i & len in case the word is too long
16067             old_i = i, old_len = len;
16068
16069             // find the end of the last word
16070             while (i && src[i] != ' ' && src[i] != '\n')
16071             {
16072                 i--;
16073                 len--;
16074             }
16075
16076             // word too long?  restore i & len before splitting it
16077             if ((old_i-i+clen) >= width)
16078             {
16079                 i = old_i;
16080                 len = old_len;
16081             }
16082
16083             // extra space?
16084             if (i && src[i-1] == ' ')
16085                 len--;
16086
16087             if (src[i] != ' ' && src[i] != '\n')
16088             {
16089                 i--;
16090                 if (len)
16091                     len--;
16092             }
16093
16094             // now append the newline and continuation sequence
16095             if (dest)
16096                 dest[len] = '\n';
16097             len++;
16098             if (dest)
16099                 strncpy(dest+len, cseq, cseq_len);
16100             len += cseq_len;
16101             line = cseq_len;
16102             clen = cseq_len;
16103             continue;
16104         }
16105
16106         if (dest)
16107             dest[len] = src[i];
16108         len++;
16109         if (!ansi)
16110             line++;
16111         if (src[i] == '\n')
16112             line = 0;
16113         if (src[i] == 'm')
16114             ansi = 0;
16115     }
16116     if (dest && appData.debugMode)
16117     {
16118         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16119             count, width, line, len, *lp);
16120         show_bytes(debugFP, src, count);
16121         fprintf(debugFP, "\ndest: ");
16122         show_bytes(debugFP, dest, len);
16123         fprintf(debugFP, "\n");
16124     }
16125     *lp = dest ? line : old_line;
16126
16127     return len;
16128 }
16129
16130 // [HGM] vari: routines for shelving variations
16131
16132 void
16133 PushInner(int firstMove, int lastMove)
16134 {
16135         int i, j, nrMoves = lastMove - firstMove;
16136
16137         // push current tail of game on stack
16138         savedResult[storedGames] = gameInfo.result;
16139         savedDetails[storedGames] = gameInfo.resultDetails;
16140         gameInfo.resultDetails = NULL;
16141         savedFirst[storedGames] = firstMove;
16142         savedLast [storedGames] = lastMove;
16143         savedFramePtr[storedGames] = framePtr;
16144         framePtr -= nrMoves; // reserve space for the boards
16145         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16146             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16147             for(j=0; j<MOVE_LEN; j++)
16148                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16149             for(j=0; j<2*MOVE_LEN; j++)
16150                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16151             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16152             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16153             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16154             pvInfoList[firstMove+i-1].depth = 0;
16155             commentList[framePtr+i] = commentList[firstMove+i];
16156             commentList[firstMove+i] = NULL;
16157         }
16158
16159         storedGames++;
16160         forwardMostMove = firstMove; // truncate game so we can start variation
16161 }
16162
16163 void
16164 PushTail(int firstMove, int lastMove)
16165 {
16166         if(appData.icsActive) { // only in local mode
16167                 forwardMostMove = currentMove; // mimic old ICS behavior
16168                 return;
16169         }
16170         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16171
16172         PushInner(firstMove, lastMove);
16173         if(storedGames == 1) GreyRevert(FALSE);
16174 }
16175
16176 void
16177 PopInner(Boolean annotate)
16178 {
16179         int i, j, nrMoves;
16180         char buf[8000], moveBuf[20];
16181
16182         storedGames--;
16183         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16184         nrMoves = savedLast[storedGames] - currentMove;
16185         if(annotate) {
16186                 int cnt = 10;
16187                 if(!WhiteOnMove(currentMove))
16188                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16189                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16190                 for(i=currentMove; i<forwardMostMove; i++) {
16191                         if(WhiteOnMove(i))
16192                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16193                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16194                         strcat(buf, moveBuf);
16195                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16196                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16197                 }
16198                 strcat(buf, ")");
16199         }
16200         for(i=1; i<=nrMoves; i++) { // copy last variation back
16201             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16202             for(j=0; j<MOVE_LEN; j++)
16203                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16204             for(j=0; j<2*MOVE_LEN; j++)
16205                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16206             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16207             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16208             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16209             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16210             commentList[currentMove+i] = commentList[framePtr+i];
16211             commentList[framePtr+i] = NULL;
16212         }
16213         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16214         framePtr = savedFramePtr[storedGames];
16215         gameInfo.result = savedResult[storedGames];
16216         if(gameInfo.resultDetails != NULL) {
16217             free(gameInfo.resultDetails);
16218       }
16219         gameInfo.resultDetails = savedDetails[storedGames];
16220         forwardMostMove = currentMove + nrMoves;
16221 }
16222
16223 Boolean
16224 PopTail(Boolean annotate)
16225 {
16226         if(appData.icsActive) return FALSE; // only in local mode
16227         if(!storedGames) return FALSE; // sanity
16228         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16229
16230         PopInner(annotate);
16231
16232         if(storedGames == 0) GreyRevert(TRUE);
16233         return TRUE;
16234 }
16235
16236 void
16237 CleanupTail()
16238 {       // remove all shelved variations
16239         int i;
16240         for(i=0; i<storedGames; i++) {
16241             if(savedDetails[i])
16242                 free(savedDetails[i]);
16243             savedDetails[i] = NULL;
16244         }
16245         for(i=framePtr; i<MAX_MOVES; i++) {
16246                 if(commentList[i]) free(commentList[i]);
16247                 commentList[i] = NULL;
16248         }
16249         framePtr = MAX_MOVES-1;
16250         storedGames = 0;
16251 }
16252
16253 void
16254 LoadVariation(int index, char *text)
16255 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16256         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16257         int level = 0, move;
16258
16259         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16260         // first find outermost bracketing variation
16261         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16262             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16263                 if(*p == '{') wait = '}'; else
16264                 if(*p == '[') wait = ']'; else
16265                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16266                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16267             }
16268             if(*p == wait) wait = NULLCHAR; // closing ]} found
16269             p++;
16270         }
16271         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16272         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16273         end[1] = NULLCHAR; // clip off comment beyond variation
16274         ToNrEvent(currentMove-1);
16275         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16276         // kludge: use ParsePV() to append variation to game
16277         move = currentMove;
16278         ParsePV(start, TRUE, TRUE);
16279         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16280         ClearPremoveHighlights();
16281         CommentPopDown();
16282         ToNrEvent(currentMove+1);
16283 }
16284