Let ParsePV always generate SAN move
[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     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5303     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5304     CoordsToAlgebraic(boards[endPV - 1],
5305                              PosFlags(endPV - 1),
5306                              fromY, fromX, toY, toX, promoChar,
5307                              parseList[endPV - 1]);
5308   } while(valid);
5309   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5310   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5311   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5312                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5313   DrawPosition(TRUE, boards[currentMove]);
5314 }
5315
5316 int
5317 MultiPV(ChessProgramState *cps)
5318 {       // check if engine supports MultiPV, and if so, return the nmber of the option that sets it
5319         int i;
5320         for(i=0; i<cps->nrOptions; i++)
5321             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5322                 return i;
5323         return -1;
5324 }
5325
5326 Boolean
5327 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5328 {
5329         int startPV, multi, lineStart, origIndex = index;
5330         char *p, buf2[MSG_SIZ];
5331
5332         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5333         lastX = x; lastY = y;
5334         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5335         lineStart = startPV = index;
5336         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5337         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5338         index = startPV;
5339         do{ while(buf[index] && buf[index] != '\n') index++;
5340         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5341         buf[index] = 0;
5342         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5343                 int n = first.option[multi].value;
5344                 if(origIndex < 10) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5345                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5346                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5347                 first.option[multi].value = n;
5348                 *start = *end = 0;
5349                 return TRUE;
5350         }
5351         ParsePV(buf+startPV, FALSE, !shiftKey);
5352         *start = startPV; *end = index-1;
5353         return TRUE;
5354 }
5355
5356 Boolean
5357 LoadPV(int x, int y)
5358 { // called on right mouse click to load PV
5359   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5360   lastX = x; lastY = y;
5361   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5362   return TRUE;
5363 }
5364
5365 void
5366 UnLoadPV()
5367 {
5368   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5369   if(endPV < 0) return;
5370   endPV = -1;
5371   if(shiftKey && gameMode == AnalyzeMode) {
5372         if(pushed) storedGames--; // abandon shelved tail of original game
5373         pushed = FALSE;
5374         forwardMostMove = currentMove;
5375         currentMove = oldFMM;
5376         ToNrEvent(forwardMostMove);
5377   }
5378   currentMove = forwardMostMove;
5379   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5380   ClearPremoveHighlights();
5381   DrawPosition(TRUE, boards[currentMove]);
5382 }
5383
5384 void
5385 MovePV(int x, int y, int h)
5386 { // step through PV based on mouse coordinates (called on mouse move)
5387   int margin = h>>3, step = 0;
5388
5389   // we must somehow check if right button is still down (might be released off board!)
5390   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5391   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5392   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5393   if(!step) return;
5394   lastX = x; lastY = y;
5395
5396   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5397   if(endPV < 0) return;
5398   if(y < margin) step = 1; else
5399   if(y > h - margin) step = -1;
5400   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5401   currentMove += step;
5402   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5403   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5404                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5405   DrawPosition(FALSE, boards[currentMove]);
5406 }
5407
5408
5409 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5410 // All positions will have equal probability, but the current method will not provide a unique
5411 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5412 #define DARK 1
5413 #define LITE 2
5414 #define ANY 3
5415
5416 int squaresLeft[4];
5417 int piecesLeft[(int)BlackPawn];
5418 int seed, nrOfShuffles;
5419
5420 void GetPositionNumber()
5421 {       // sets global variable seed
5422         int i;
5423
5424         seed = appData.defaultFrcPosition;
5425         if(seed < 0) { // randomize based on time for negative FRC position numbers
5426                 for(i=0; i<50; i++) seed += random();
5427                 seed = random() ^ random() >> 8 ^ random() << 8;
5428                 if(seed<0) seed = -seed;
5429         }
5430 }
5431
5432 int put(Board board, int pieceType, int rank, int n, int shade)
5433 // put the piece on the (n-1)-th empty squares of the given shade
5434 {
5435         int i;
5436
5437         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5438                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5439                         board[rank][i] = (ChessSquare) pieceType;
5440                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5441                         squaresLeft[ANY]--;
5442                         piecesLeft[pieceType]--;
5443                         return i;
5444                 }
5445         }
5446         return -1;
5447 }
5448
5449
5450 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5451 // calculate where the next piece goes, (any empty square), and put it there
5452 {
5453         int i;
5454
5455         i = seed % squaresLeft[shade];
5456         nrOfShuffles *= squaresLeft[shade];
5457         seed /= squaresLeft[shade];
5458         put(board, pieceType, rank, i, shade);
5459 }
5460
5461 void AddTwoPieces(Board board, int pieceType, int rank)
5462 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5463 {
5464         int i, n=squaresLeft[ANY], j=n-1, k;
5465
5466         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5467         i = seed % k;  // pick one
5468         nrOfShuffles *= k;
5469         seed /= k;
5470         while(i >= j) i -= j--;
5471         j = n - 1 - j; i += j;
5472         put(board, pieceType, rank, j, ANY);
5473         put(board, pieceType, rank, i, ANY);
5474 }
5475
5476 void SetUpShuffle(Board board, int number)
5477 {
5478         int i, p, first=1;
5479
5480         GetPositionNumber(); nrOfShuffles = 1;
5481
5482         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5483         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5484         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5485
5486         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5487
5488         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5489             p = (int) board[0][i];
5490             if(p < (int) BlackPawn) piecesLeft[p] ++;
5491             board[0][i] = EmptySquare;
5492         }
5493
5494         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5495             // shuffles restricted to allow normal castling put KRR first
5496             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5497                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5498             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5499                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5500             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5501                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5502             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5503                 put(board, WhiteRook, 0, 0, ANY);
5504             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5505         }
5506
5507         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5508             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5509             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5510                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5511                 while(piecesLeft[p] >= 2) {
5512                     AddOnePiece(board, p, 0, LITE);
5513                     AddOnePiece(board, p, 0, DARK);
5514                 }
5515                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5516             }
5517
5518         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5519             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5520             // but we leave King and Rooks for last, to possibly obey FRC restriction
5521             if(p == (int)WhiteRook) continue;
5522             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5523             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5524         }
5525
5526         // now everything is placed, except perhaps King (Unicorn) and Rooks
5527
5528         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5529             // Last King gets castling rights
5530             while(piecesLeft[(int)WhiteUnicorn]) {
5531                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5532                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5533             }
5534
5535             while(piecesLeft[(int)WhiteKing]) {
5536                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5537                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5538             }
5539
5540
5541         } else {
5542             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5543             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5544         }
5545
5546         // Only Rooks can be left; simply place them all
5547         while(piecesLeft[(int)WhiteRook]) {
5548                 i = put(board, WhiteRook, 0, 0, ANY);
5549                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5550                         if(first) {
5551                                 first=0;
5552                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5553                         }
5554                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5555                 }
5556         }
5557         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5558             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5559         }
5560
5561         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5562 }
5563
5564 int SetCharTable( char *table, const char * map )
5565 /* [HGM] moved here from winboard.c because of its general usefulness */
5566 /*       Basically a safe strcpy that uses the last character as King */
5567 {
5568     int result = FALSE; int NrPieces;
5569
5570     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5571                     && NrPieces >= 12 && !(NrPieces&1)) {
5572         int i; /* [HGM] Accept even length from 12 to 34 */
5573
5574         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5575         for( i=0; i<NrPieces/2-1; i++ ) {
5576             table[i] = map[i];
5577             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5578         }
5579         table[(int) WhiteKing]  = map[NrPieces/2-1];
5580         table[(int) BlackKing]  = map[NrPieces-1];
5581
5582         result = TRUE;
5583     }
5584
5585     return result;
5586 }
5587
5588 void Prelude(Board board)
5589 {       // [HGM] superchess: random selection of exo-pieces
5590         int i, j, k; ChessSquare p;
5591         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5592
5593         GetPositionNumber(); // use FRC position number
5594
5595         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5596             SetCharTable(pieceToChar, appData.pieceToCharTable);
5597             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5598                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5599         }
5600
5601         j = seed%4;                 seed /= 4;
5602         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5603         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5604         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5605         j = seed%3 + (seed%3 >= j); seed /= 3;
5606         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5607         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5608         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5609         j = seed%3;                 seed /= 3;
5610         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5611         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5612         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5613         j = seed%2 + (seed%2 >= j); seed /= 2;
5614         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5615         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5616         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5617         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5618         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5619         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5620         put(board, exoPieces[0],    0, 0, ANY);
5621         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5622 }
5623
5624 void
5625 InitPosition(redraw)
5626      int redraw;
5627 {
5628     ChessSquare (* pieces)[BOARD_FILES];
5629     int i, j, pawnRow, overrule,
5630     oldx = gameInfo.boardWidth,
5631     oldy = gameInfo.boardHeight,
5632     oldh = gameInfo.holdingsWidth;
5633     static int oldv;
5634
5635     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5636
5637     /* [AS] Initialize pv info list [HGM] and game status */
5638     {
5639         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5640             pvInfoList[i].depth = 0;
5641             boards[i][EP_STATUS] = EP_NONE;
5642             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5643         }
5644
5645         initialRulePlies = 0; /* 50-move counter start */
5646
5647         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5648         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5649     }
5650
5651
5652     /* [HGM] logic here is completely changed. In stead of full positions */
5653     /* the initialized data only consist of the two backranks. The switch */
5654     /* selects which one we will use, which is than copied to the Board   */
5655     /* initialPosition, which for the rest is initialized by Pawns and    */
5656     /* empty squares. This initial position is then copied to boards[0],  */
5657     /* possibly after shuffling, so that it remains available.            */
5658
5659     gameInfo.holdingsWidth = 0; /* default board sizes */
5660     gameInfo.boardWidth    = 8;
5661     gameInfo.boardHeight   = 8;
5662     gameInfo.holdingsSize  = 0;
5663     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5664     for(i=0; i<BOARD_FILES-2; i++)
5665       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5666     initialPosition[EP_STATUS] = EP_NONE;
5667     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5668     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5669          SetCharTable(pieceNickName, appData.pieceNickNames);
5670     else SetCharTable(pieceNickName, "............");
5671     pieces = FIDEArray;
5672
5673     switch (gameInfo.variant) {
5674     case VariantFischeRandom:
5675       shuffleOpenings = TRUE;
5676     default:
5677       break;
5678     case VariantShatranj:
5679       pieces = ShatranjArray;
5680       nrCastlingRights = 0;
5681       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5682       break;
5683     case VariantMakruk:
5684       pieces = makrukArray;
5685       nrCastlingRights = 0;
5686       startedFromSetupPosition = TRUE;
5687       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5688       break;
5689     case VariantTwoKings:
5690       pieces = twoKingsArray;
5691       break;
5692     case VariantCapaRandom:
5693       shuffleOpenings = TRUE;
5694     case VariantCapablanca:
5695       pieces = CapablancaArray;
5696       gameInfo.boardWidth = 10;
5697       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5698       break;
5699     case VariantGothic:
5700       pieces = GothicArray;
5701       gameInfo.boardWidth = 10;
5702       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5703       break;
5704     case VariantSChess:
5705       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5706       gameInfo.holdingsSize = 7;
5707       break;
5708     case VariantJanus:
5709       pieces = JanusArray;
5710       gameInfo.boardWidth = 10;
5711       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5712       nrCastlingRights = 6;
5713         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5714         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5715         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5716         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5717         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5718         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5719       break;
5720     case VariantFalcon:
5721       pieces = FalconArray;
5722       gameInfo.boardWidth = 10;
5723       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5724       break;
5725     case VariantXiangqi:
5726       pieces = XiangqiArray;
5727       gameInfo.boardWidth  = 9;
5728       gameInfo.boardHeight = 10;
5729       nrCastlingRights = 0;
5730       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5731       break;
5732     case VariantShogi:
5733       pieces = ShogiArray;
5734       gameInfo.boardWidth  = 9;
5735       gameInfo.boardHeight = 9;
5736       gameInfo.holdingsSize = 7;
5737       nrCastlingRights = 0;
5738       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5739       break;
5740     case VariantCourier:
5741       pieces = CourierArray;
5742       gameInfo.boardWidth  = 12;
5743       nrCastlingRights = 0;
5744       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5745       break;
5746     case VariantKnightmate:
5747       pieces = KnightmateArray;
5748       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5749       break;
5750     case VariantSpartan:
5751       pieces = SpartanArray;
5752       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5753       break;
5754     case VariantFairy:
5755       pieces = fairyArray;
5756       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5757       break;
5758     case VariantGreat:
5759       pieces = GreatArray;
5760       gameInfo.boardWidth = 10;
5761       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5762       gameInfo.holdingsSize = 8;
5763       break;
5764     case VariantSuper:
5765       pieces = FIDEArray;
5766       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5767       gameInfo.holdingsSize = 8;
5768       startedFromSetupPosition = TRUE;
5769       break;
5770     case VariantCrazyhouse:
5771     case VariantBughouse:
5772       pieces = FIDEArray;
5773       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5774       gameInfo.holdingsSize = 5;
5775       break;
5776     case VariantWildCastle:
5777       pieces = FIDEArray;
5778       /* !!?shuffle with kings guaranteed to be on d or e file */
5779       shuffleOpenings = 1;
5780       break;
5781     case VariantNoCastle:
5782       pieces = FIDEArray;
5783       nrCastlingRights = 0;
5784       /* !!?unconstrained back-rank shuffle */
5785       shuffleOpenings = 1;
5786       break;
5787     }
5788
5789     overrule = 0;
5790     if(appData.NrFiles >= 0) {
5791         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5792         gameInfo.boardWidth = appData.NrFiles;
5793     }
5794     if(appData.NrRanks >= 0) {
5795         gameInfo.boardHeight = appData.NrRanks;
5796     }
5797     if(appData.holdingsSize >= 0) {
5798         i = appData.holdingsSize;
5799         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5800         gameInfo.holdingsSize = i;
5801     }
5802     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5803     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5804         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5805
5806     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5807     if(pawnRow < 1) pawnRow = 1;
5808     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5809
5810     /* User pieceToChar list overrules defaults */
5811     if(appData.pieceToCharTable != NULL)
5812         SetCharTable(pieceToChar, appData.pieceToCharTable);
5813
5814     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5815
5816         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5817             s = (ChessSquare) 0; /* account holding counts in guard band */
5818         for( i=0; i<BOARD_HEIGHT; i++ )
5819             initialPosition[i][j] = s;
5820
5821         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5822         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5823         initialPosition[pawnRow][j] = WhitePawn;
5824         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5825         if(gameInfo.variant == VariantXiangqi) {
5826             if(j&1) {
5827                 initialPosition[pawnRow][j] =
5828                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5829                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5830                    initialPosition[2][j] = WhiteCannon;
5831                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5832                 }
5833             }
5834         }
5835         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5836     }
5837     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5838
5839             j=BOARD_LEFT+1;
5840             initialPosition[1][j] = WhiteBishop;
5841             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5842             j=BOARD_RGHT-2;
5843             initialPosition[1][j] = WhiteRook;
5844             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5845     }
5846
5847     if( nrCastlingRights == -1) {
5848         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5849         /*       This sets default castling rights from none to normal corners   */
5850         /* Variants with other castling rights must set them themselves above    */
5851         nrCastlingRights = 6;
5852
5853         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5854         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5855         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5856         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5857         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5858         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5859      }
5860
5861      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5862      if(gameInfo.variant == VariantGreat) { // promotion commoners
5863         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5864         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5865         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5866         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5867      }
5868      if( gameInfo.variant == VariantSChess ) {
5869       initialPosition[1][0] = BlackMarshall;
5870       initialPosition[2][0] = BlackAngel;
5871       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5872       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5873       initialPosition[1][1] = initialPosition[2][1] = 
5874       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5875      }
5876   if (appData.debugMode) {
5877     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5878   }
5879     if(shuffleOpenings) {
5880         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5881         startedFromSetupPosition = TRUE;
5882     }
5883     if(startedFromPositionFile) {
5884       /* [HGM] loadPos: use PositionFile for every new game */
5885       CopyBoard(initialPosition, filePosition);
5886       for(i=0; i<nrCastlingRights; i++)
5887           initialRights[i] = filePosition[CASTLING][i];
5888       startedFromSetupPosition = TRUE;
5889     }
5890
5891     CopyBoard(boards[0], initialPosition);
5892
5893     if(oldx != gameInfo.boardWidth ||
5894        oldy != gameInfo.boardHeight ||
5895        oldv != gameInfo.variant ||
5896        oldh != gameInfo.holdingsWidth
5897                                          )
5898             InitDrawingSizes(-2 ,0);
5899
5900     oldv = gameInfo.variant;
5901     if (redraw)
5902       DrawPosition(TRUE, boards[currentMove]);
5903 }
5904
5905 void
5906 SendBoard(cps, moveNum)
5907      ChessProgramState *cps;
5908      int moveNum;
5909 {
5910     char message[MSG_SIZ];
5911
5912     if (cps->useSetboard) {
5913       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5914       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5915       SendToProgram(message, cps);
5916       free(fen);
5917
5918     } else {
5919       ChessSquare *bp;
5920       int i, j;
5921       /* Kludge to set black to move, avoiding the troublesome and now
5922        * deprecated "black" command.
5923        */
5924       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5925         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5926
5927       SendToProgram("edit\n", cps);
5928       SendToProgram("#\n", cps);
5929       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5930         bp = &boards[moveNum][i][BOARD_LEFT];
5931         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5932           if ((int) *bp < (int) BlackPawn) {
5933             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5934                     AAA + j, ONE + i);
5935             if(message[0] == '+' || message[0] == '~') {
5936               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5937                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5938                         AAA + j, ONE + i);
5939             }
5940             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5941                 message[1] = BOARD_RGHT   - 1 - j + '1';
5942                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5943             }
5944             SendToProgram(message, cps);
5945           }
5946         }
5947       }
5948
5949       SendToProgram("c\n", cps);
5950       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5951         bp = &boards[moveNum][i][BOARD_LEFT];
5952         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5953           if (((int) *bp != (int) EmptySquare)
5954               && ((int) *bp >= (int) BlackPawn)) {
5955             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5956                     AAA + j, ONE + i);
5957             if(message[0] == '+' || message[0] == '~') {
5958               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5959                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5960                         AAA + j, ONE + i);
5961             }
5962             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5963                 message[1] = BOARD_RGHT   - 1 - j + '1';
5964                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5965             }
5966             SendToProgram(message, cps);
5967           }
5968         }
5969       }
5970
5971       SendToProgram(".\n", cps);
5972     }
5973     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5974 }
5975
5976 ChessSquare
5977 DefaultPromoChoice(int white)
5978 {
5979     ChessSquare result;
5980     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5981         result = WhiteFerz; // no choice
5982     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5983         result= WhiteKing; // in Suicide Q is the last thing we want
5984     else if(gameInfo.variant == VariantSpartan)
5985         result = white ? WhiteQueen : WhiteAngel;
5986     else result = WhiteQueen;
5987     if(!white) result = WHITE_TO_BLACK result;
5988     return result;
5989 }
5990
5991 static int autoQueen; // [HGM] oneclick
5992
5993 int
5994 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5995 {
5996     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5997     /* [HGM] add Shogi promotions */
5998     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5999     ChessSquare piece;
6000     ChessMove moveType;
6001     Boolean premove;
6002
6003     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6004     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6005
6006     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6007       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6008         return FALSE;
6009
6010     piece = boards[currentMove][fromY][fromX];
6011     if(gameInfo.variant == VariantShogi) {
6012         promotionZoneSize = BOARD_HEIGHT/3;
6013         highestPromotingPiece = (int)WhiteFerz;
6014     } else if(gameInfo.variant == VariantMakruk) {
6015         promotionZoneSize = 3;
6016     }
6017
6018     // Treat Lance as Pawn when it is not representing Amazon
6019     if(gameInfo.variant != VariantSuper) {
6020         if(piece == WhiteLance) piece = WhitePawn; else
6021         if(piece == BlackLance) piece = BlackPawn;
6022     }
6023
6024     // next weed out all moves that do not touch the promotion zone at all
6025     if((int)piece >= BlackPawn) {
6026         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6027              return FALSE;
6028         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6029     } else {
6030         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6031            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6032     }
6033
6034     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6035
6036     // weed out mandatory Shogi promotions
6037     if(gameInfo.variant == VariantShogi) {
6038         if(piece >= BlackPawn) {
6039             if(toY == 0 && piece == BlackPawn ||
6040                toY == 0 && piece == BlackQueen ||
6041                toY <= 1 && piece == BlackKnight) {
6042                 *promoChoice = '+';
6043                 return FALSE;
6044             }
6045         } else {
6046             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6047                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6048                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6049                 *promoChoice = '+';
6050                 return FALSE;
6051             }
6052         }
6053     }
6054
6055     // weed out obviously illegal Pawn moves
6056     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6057         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6058         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6059         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6060         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6061         // note we are not allowed to test for valid (non-)capture, due to premove
6062     }
6063
6064     // we either have a choice what to promote to, or (in Shogi) whether to promote
6065     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6066         *promoChoice = PieceToChar(BlackFerz);  // no choice
6067         return FALSE;
6068     }
6069     // no sense asking what we must promote to if it is going to explode...
6070     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6071         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6072         return FALSE;
6073     }
6074     // give caller the default choice even if we will not make it
6075     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6076     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6077     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6078                            && gameInfo.variant != VariantShogi
6079                            && gameInfo.variant != VariantSuper) return FALSE;
6080     if(autoQueen) return FALSE; // predetermined
6081
6082     // suppress promotion popup on illegal moves that are not premoves
6083     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6084               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6085     if(appData.testLegality && !premove) {
6086         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6087                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6088         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6089             return FALSE;
6090     }
6091
6092     return TRUE;
6093 }
6094
6095 int
6096 InPalace(row, column)
6097      int row, column;
6098 {   /* [HGM] for Xiangqi */
6099     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6100          column < (BOARD_WIDTH + 4)/2 &&
6101          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6102     return FALSE;
6103 }
6104
6105 int
6106 PieceForSquare (x, y)
6107      int x;
6108      int y;
6109 {
6110   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6111      return -1;
6112   else
6113      return boards[currentMove][y][x];
6114 }
6115
6116 int
6117 OKToStartUserMove(x, y)
6118      int x, y;
6119 {
6120     ChessSquare from_piece;
6121     int white_piece;
6122
6123     if (matchMode) return FALSE;
6124     if (gameMode == EditPosition) return TRUE;
6125
6126     if (x >= 0 && y >= 0)
6127       from_piece = boards[currentMove][y][x];
6128     else
6129       from_piece = EmptySquare;
6130
6131     if (from_piece == EmptySquare) return FALSE;
6132
6133     white_piece = (int)from_piece >= (int)WhitePawn &&
6134       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6135
6136     switch (gameMode) {
6137       case PlayFromGameFile:
6138       case AnalyzeFile:
6139       case TwoMachinesPlay:
6140       case EndOfGame:
6141         return FALSE;
6142
6143       case IcsObserving:
6144       case IcsIdle:
6145         return FALSE;
6146
6147       case MachinePlaysWhite:
6148       case IcsPlayingBlack:
6149         if (appData.zippyPlay) return FALSE;
6150         if (white_piece) {
6151             DisplayMoveError(_("You are playing Black"));
6152             return FALSE;
6153         }
6154         break;
6155
6156       case MachinePlaysBlack:
6157       case IcsPlayingWhite:
6158         if (appData.zippyPlay) return FALSE;
6159         if (!white_piece) {
6160             DisplayMoveError(_("You are playing White"));
6161             return FALSE;
6162         }
6163         break;
6164
6165       case EditGame:
6166         if (!white_piece && WhiteOnMove(currentMove)) {
6167             DisplayMoveError(_("It is White's turn"));
6168             return FALSE;
6169         }
6170         if (white_piece && !WhiteOnMove(currentMove)) {
6171             DisplayMoveError(_("It is Black's turn"));
6172             return FALSE;
6173         }
6174         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6175             /* Editing correspondence game history */
6176             /* Could disallow this or prompt for confirmation */
6177             cmailOldMove = -1;
6178         }
6179         break;
6180
6181       case BeginningOfGame:
6182         if (appData.icsActive) return FALSE;
6183         if (!appData.noChessProgram) {
6184             if (!white_piece) {
6185                 DisplayMoveError(_("You are playing White"));
6186                 return FALSE;
6187             }
6188         }
6189         break;
6190
6191       case Training:
6192         if (!white_piece && WhiteOnMove(currentMove)) {
6193             DisplayMoveError(_("It is White's turn"));
6194             return FALSE;
6195         }
6196         if (white_piece && !WhiteOnMove(currentMove)) {
6197             DisplayMoveError(_("It is Black's turn"));
6198             return FALSE;
6199         }
6200         break;
6201
6202       default:
6203       case IcsExamining:
6204         break;
6205     }
6206     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6207         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6208         && gameMode != AnalyzeFile && gameMode != Training) {
6209         DisplayMoveError(_("Displayed position is not current"));
6210         return FALSE;
6211     }
6212     return TRUE;
6213 }
6214
6215 Boolean
6216 OnlyMove(int *x, int *y, Boolean captures) {
6217     DisambiguateClosure cl;
6218     if (appData.zippyPlay) return FALSE;
6219     switch(gameMode) {
6220       case MachinePlaysBlack:
6221       case IcsPlayingWhite:
6222       case BeginningOfGame:
6223         if(!WhiteOnMove(currentMove)) return FALSE;
6224         break;
6225       case MachinePlaysWhite:
6226       case IcsPlayingBlack:
6227         if(WhiteOnMove(currentMove)) return FALSE;
6228         break;
6229       case EditGame:
6230         break;
6231       default:
6232         return FALSE;
6233     }
6234     cl.pieceIn = EmptySquare;
6235     cl.rfIn = *y;
6236     cl.ffIn = *x;
6237     cl.rtIn = -1;
6238     cl.ftIn = -1;
6239     cl.promoCharIn = NULLCHAR;
6240     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6241     if( cl.kind == NormalMove ||
6242         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6243         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6244         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6245       fromX = cl.ff;
6246       fromY = cl.rf;
6247       *x = cl.ft;
6248       *y = cl.rt;
6249       return TRUE;
6250     }
6251     if(cl.kind != ImpossibleMove) return FALSE;
6252     cl.pieceIn = EmptySquare;
6253     cl.rfIn = -1;
6254     cl.ffIn = -1;
6255     cl.rtIn = *y;
6256     cl.ftIn = *x;
6257     cl.promoCharIn = NULLCHAR;
6258     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6259     if( cl.kind == NormalMove ||
6260         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6261         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6262         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6263       fromX = cl.ff;
6264       fromY = cl.rf;
6265       *x = cl.ft;
6266       *y = cl.rt;
6267       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6268       return TRUE;
6269     }
6270     return FALSE;
6271 }
6272
6273 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6274 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6275 int lastLoadGameUseList = FALSE;
6276 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6277 ChessMove lastLoadGameStart = EndOfFile;
6278
6279 void
6280 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6281      int fromX, fromY, toX, toY;
6282      int promoChar;
6283 {
6284     ChessMove moveType;
6285     ChessSquare pdown, pup;
6286
6287     /* Check if the user is playing in turn.  This is complicated because we
6288        let the user "pick up" a piece before it is his turn.  So the piece he
6289        tried to pick up may have been captured by the time he puts it down!
6290        Therefore we use the color the user is supposed to be playing in this
6291        test, not the color of the piece that is currently on the starting
6292        square---except in EditGame mode, where the user is playing both
6293        sides; fortunately there the capture race can't happen.  (It can
6294        now happen in IcsExamining mode, but that's just too bad.  The user
6295        will get a somewhat confusing message in that case.)
6296        */
6297
6298     switch (gameMode) {
6299       case PlayFromGameFile:
6300       case AnalyzeFile:
6301       case TwoMachinesPlay:
6302       case EndOfGame:
6303       case IcsObserving:
6304       case IcsIdle:
6305         /* We switched into a game mode where moves are not accepted,
6306            perhaps while the mouse button was down. */
6307         return;
6308
6309       case MachinePlaysWhite:
6310         /* User is moving for Black */
6311         if (WhiteOnMove(currentMove)) {
6312             DisplayMoveError(_("It is White's turn"));
6313             return;
6314         }
6315         break;
6316
6317       case MachinePlaysBlack:
6318         /* User is moving for White */
6319         if (!WhiteOnMove(currentMove)) {
6320             DisplayMoveError(_("It is Black's turn"));
6321             return;
6322         }
6323         break;
6324
6325       case EditGame:
6326       case IcsExamining:
6327       case BeginningOfGame:
6328       case AnalyzeMode:
6329       case Training:
6330         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6331         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6332             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6333             /* User is moving for Black */
6334             if (WhiteOnMove(currentMove)) {
6335                 DisplayMoveError(_("It is White's turn"));
6336                 return;
6337             }
6338         } else {
6339             /* User is moving for White */
6340             if (!WhiteOnMove(currentMove)) {
6341                 DisplayMoveError(_("It is Black's turn"));
6342                 return;
6343             }
6344         }
6345         break;
6346
6347       case IcsPlayingBlack:
6348         /* User is moving for Black */
6349         if (WhiteOnMove(currentMove)) {
6350             if (!appData.premove) {
6351                 DisplayMoveError(_("It is White's turn"));
6352             } else if (toX >= 0 && toY >= 0) {
6353                 premoveToX = toX;
6354                 premoveToY = toY;
6355                 premoveFromX = fromX;
6356                 premoveFromY = fromY;
6357                 premovePromoChar = promoChar;
6358                 gotPremove = 1;
6359                 if (appData.debugMode)
6360                     fprintf(debugFP, "Got premove: fromX %d,"
6361                             "fromY %d, toX %d, toY %d\n",
6362                             fromX, fromY, toX, toY);
6363             }
6364             return;
6365         }
6366         break;
6367
6368       case IcsPlayingWhite:
6369         /* User is moving for White */
6370         if (!WhiteOnMove(currentMove)) {
6371             if (!appData.premove) {
6372                 DisplayMoveError(_("It is Black's turn"));
6373             } else if (toX >= 0 && toY >= 0) {
6374                 premoveToX = toX;
6375                 premoveToY = toY;
6376                 premoveFromX = fromX;
6377                 premoveFromY = fromY;
6378                 premovePromoChar = promoChar;
6379                 gotPremove = 1;
6380                 if (appData.debugMode)
6381                     fprintf(debugFP, "Got premove: fromX %d,"
6382                             "fromY %d, toX %d, toY %d\n",
6383                             fromX, fromY, toX, toY);
6384             }
6385             return;
6386         }
6387         break;
6388
6389       default:
6390         break;
6391
6392       case EditPosition:
6393         /* EditPosition, empty square, or different color piece;
6394            click-click move is possible */
6395         if (toX == -2 || toY == -2) {
6396             boards[0][fromY][fromX] = EmptySquare;
6397             DrawPosition(FALSE, boards[currentMove]);
6398             return;
6399         } else if (toX >= 0 && toY >= 0) {
6400             boards[0][toY][toX] = boards[0][fromY][fromX];
6401             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6402                 if(boards[0][fromY][0] != EmptySquare) {
6403                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6404                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6405                 }
6406             } else
6407             if(fromX == BOARD_RGHT+1) {
6408                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6409                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6410                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6411                 }
6412             } else
6413             boards[0][fromY][fromX] = EmptySquare;
6414             DrawPosition(FALSE, boards[currentMove]);
6415             return;
6416         }
6417         return;
6418     }
6419
6420     if(toX < 0 || toY < 0) return;
6421     pdown = boards[currentMove][fromY][fromX];
6422     pup = boards[currentMove][toY][toX];
6423
6424     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6425     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6426          if( pup != EmptySquare ) return;
6427          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6428            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6429                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6430            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6431            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6432            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6433            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6434          fromY = DROP_RANK;
6435     }
6436
6437     /* [HGM] always test for legality, to get promotion info */
6438     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6439                                          fromY, fromX, toY, toX, promoChar);
6440     /* [HGM] but possibly ignore an IllegalMove result */
6441     if (appData.testLegality) {
6442         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6443             DisplayMoveError(_("Illegal move"));
6444             return;
6445         }
6446     }
6447
6448     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6449 }
6450
6451 /* Common tail of UserMoveEvent and DropMenuEvent */
6452 int
6453 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6454      ChessMove moveType;
6455      int fromX, fromY, toX, toY;
6456      /*char*/int promoChar;
6457 {
6458     char *bookHit = 0;
6459
6460     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6461         // [HGM] superchess: suppress promotions to non-available piece
6462         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6463         if(WhiteOnMove(currentMove)) {
6464             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6465         } else {
6466             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6467         }
6468     }
6469
6470     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6471        move type in caller when we know the move is a legal promotion */
6472     if(moveType == NormalMove && promoChar)
6473         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6474
6475     /* [HGM] <popupFix> The following if has been moved here from
6476        UserMoveEvent(). Because it seemed to belong here (why not allow
6477        piece drops in training games?), and because it can only be
6478        performed after it is known to what we promote. */
6479     if (gameMode == Training) {
6480       /* compare the move played on the board to the next move in the
6481        * game. If they match, display the move and the opponent's response.
6482        * If they don't match, display an error message.
6483        */
6484       int saveAnimate;
6485       Board testBoard;
6486       CopyBoard(testBoard, boards[currentMove]);
6487       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6488
6489       if (CompareBoards(testBoard, boards[currentMove+1])) {
6490         ForwardInner(currentMove+1);
6491
6492         /* Autoplay the opponent's response.
6493          * if appData.animate was TRUE when Training mode was entered,
6494          * the response will be animated.
6495          */
6496         saveAnimate = appData.animate;
6497         appData.animate = animateTraining;
6498         ForwardInner(currentMove+1);
6499         appData.animate = saveAnimate;
6500
6501         /* check for the end of the game */
6502         if (currentMove >= forwardMostMove) {
6503           gameMode = PlayFromGameFile;
6504           ModeHighlight();
6505           SetTrainingModeOff();
6506           DisplayInformation(_("End of game"));
6507         }
6508       } else {
6509         DisplayError(_("Incorrect move"), 0);
6510       }
6511       return 1;
6512     }
6513
6514   /* Ok, now we know that the move is good, so we can kill
6515      the previous line in Analysis Mode */
6516   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6517                                 && currentMove < forwardMostMove) {
6518     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6519     else forwardMostMove = currentMove;
6520   }
6521
6522   /* If we need the chess program but it's dead, restart it */
6523   ResurrectChessProgram();
6524
6525   /* A user move restarts a paused game*/
6526   if (pausing)
6527     PauseEvent();
6528
6529   thinkOutput[0] = NULLCHAR;
6530
6531   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6532
6533   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6534     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6535     return 1;
6536   }
6537
6538   if (gameMode == BeginningOfGame) {
6539     if (appData.noChessProgram) {
6540       gameMode = EditGame;
6541       SetGameInfo();
6542     } else {
6543       char buf[MSG_SIZ];
6544       gameMode = MachinePlaysBlack;
6545       StartClocks();
6546       SetGameInfo();
6547       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6548       DisplayTitle(buf);
6549       if (first.sendName) {
6550         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6551         SendToProgram(buf, &first);
6552       }
6553       StartClocks();
6554     }
6555     ModeHighlight();
6556   }
6557
6558   /* Relay move to ICS or chess engine */
6559   if (appData.icsActive) {
6560     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6561         gameMode == IcsExamining) {
6562       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6563         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6564         SendToICS("draw ");
6565         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6566       }
6567       // also send plain move, in case ICS does not understand atomic claims
6568       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6569       ics_user_moved = 1;
6570     }
6571   } else {
6572     if (first.sendTime && (gameMode == BeginningOfGame ||
6573                            gameMode == MachinePlaysWhite ||
6574                            gameMode == MachinePlaysBlack)) {
6575       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6576     }
6577     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6578          // [HGM] book: if program might be playing, let it use book
6579         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6580         first.maybeThinking = TRUE;
6581     } else SendMoveToProgram(forwardMostMove-1, &first);
6582     if (currentMove == cmailOldMove + 1) {
6583       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6584     }
6585   }
6586
6587   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6588
6589   switch (gameMode) {
6590   case EditGame:
6591     if(appData.testLegality)
6592     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6593     case MT_NONE:
6594     case MT_CHECK:
6595       break;
6596     case MT_CHECKMATE:
6597     case MT_STAINMATE:
6598       if (WhiteOnMove(currentMove)) {
6599         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6600       } else {
6601         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6602       }
6603       break;
6604     case MT_STALEMATE:
6605       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6606       break;
6607     }
6608     break;
6609
6610   case MachinePlaysBlack:
6611   case MachinePlaysWhite:
6612     /* disable certain menu options while machine is thinking */
6613     SetMachineThinkingEnables();
6614     break;
6615
6616   default:
6617     break;
6618   }
6619
6620   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6621   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6622
6623   if(bookHit) { // [HGM] book: simulate book reply
6624         static char bookMove[MSG_SIZ]; // a bit generous?
6625
6626         programStats.nodes = programStats.depth = programStats.time =
6627         programStats.score = programStats.got_only_move = 0;
6628         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6629
6630         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6631         strcat(bookMove, bookHit);
6632         HandleMachineMove(bookMove, &first);
6633   }
6634   return 1;
6635 }
6636
6637 void
6638 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6639      Board board;
6640      int flags;
6641      ChessMove kind;
6642      int rf, ff, rt, ft;
6643      VOIDSTAR closure;
6644 {
6645     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6646     Markers *m = (Markers *) closure;
6647     if(rf == fromY && ff == fromX)
6648         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6649                          || kind == WhiteCapturesEnPassant
6650                          || kind == BlackCapturesEnPassant);
6651     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6652 }
6653
6654 void
6655 MarkTargetSquares(int clear)
6656 {
6657   int x, y;
6658   if(!appData.markers || !appData.highlightDragging ||
6659      !appData.testLegality || gameMode == EditPosition) return;
6660   if(clear) {
6661     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6662   } else {
6663     int capt = 0;
6664     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6665     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6666       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6667       if(capt)
6668       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6669     }
6670   }
6671   DrawPosition(TRUE, NULL);
6672 }
6673
6674 int
6675 Explode(Board board, int fromX, int fromY, int toX, int toY)
6676 {
6677     if(gameInfo.variant == VariantAtomic &&
6678        (board[toY][toX] != EmptySquare ||                     // capture?
6679         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6680                          board[fromY][fromX] == BlackPawn   )
6681       )) {
6682         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6683         return TRUE;
6684     }
6685     return FALSE;
6686 }
6687
6688 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6689
6690 int CanPromote(ChessSquare piece, int y)
6691 {
6692         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6693         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6694         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6695            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6696            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6697                                                   gameInfo.variant == VariantMakruk) return FALSE;
6698         return (piece == BlackPawn && y == 1 ||
6699                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6700                 piece == BlackLance && y == 1 ||
6701                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6702 }
6703
6704 void LeftClick(ClickType clickType, int xPix, int yPix)
6705 {
6706     int x, y;
6707     Boolean saveAnimate;
6708     static int second = 0, promotionChoice = 0, clearFlag = 0;
6709     char promoChoice = NULLCHAR;
6710     ChessSquare piece;
6711
6712     if(appData.seekGraph && appData.icsActive && loggedOn &&
6713         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6714         SeekGraphClick(clickType, xPix, yPix, 0);
6715         return;
6716     }
6717
6718     if (clickType == Press) ErrorPopDown();
6719     MarkTargetSquares(1);
6720
6721     x = EventToSquare(xPix, BOARD_WIDTH);
6722     y = EventToSquare(yPix, BOARD_HEIGHT);
6723     if (!flipView && y >= 0) {
6724         y = BOARD_HEIGHT - 1 - y;
6725     }
6726     if (flipView && x >= 0) {
6727         x = BOARD_WIDTH - 1 - x;
6728     }
6729
6730     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6731         defaultPromoChoice = promoSweep;
6732         promoSweep = EmptySquare;   // terminate sweep
6733         promoDefaultAltered = TRUE;
6734         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6735     }
6736
6737     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6738         if(clickType == Release) return; // ignore upclick of click-click destination
6739         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6740         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6741         if(gameInfo.holdingsWidth &&
6742                 (WhiteOnMove(currentMove)
6743                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6744                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6745             // click in right holdings, for determining promotion piece
6746             ChessSquare p = boards[currentMove][y][x];
6747             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6748             if(p != EmptySquare) {
6749                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6750                 fromX = fromY = -1;
6751                 return;
6752             }
6753         }
6754         DrawPosition(FALSE, boards[currentMove]);
6755         return;
6756     }
6757
6758     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6759     if(clickType == Press
6760             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6761               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6762               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6763         return;
6764
6765     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6766         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6767
6768     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6769         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6770                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6771         defaultPromoChoice = DefaultPromoChoice(side);
6772     }
6773
6774     autoQueen = appData.alwaysPromoteToQueen;
6775
6776     if (fromX == -1) {
6777       int originalY = y;
6778       gatingPiece = EmptySquare;
6779       if (clickType != Press) {
6780         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6781             DragPieceEnd(xPix, yPix); dragging = 0;
6782             DrawPosition(FALSE, NULL);
6783         }
6784         return;
6785       }
6786       fromX = x; fromY = y;
6787       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6788          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6789          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6790             /* First square */
6791             if (OKToStartUserMove(fromX, fromY)) {
6792                 second = 0;
6793                 MarkTargetSquares(0);
6794                 DragPieceBegin(xPix, yPix); dragging = 1;
6795                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6796                     promoSweep = defaultPromoChoice;
6797                     selectFlag = 0; lastX = xPix; lastY = yPix;
6798                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6799                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6800                 }
6801                 if (appData.highlightDragging) {
6802                     SetHighlights(fromX, fromY, -1, -1);
6803                 }
6804             } else fromX = fromY = -1;
6805             return;
6806         }
6807     }
6808
6809     /* fromX != -1 */
6810     if (clickType == Press && gameMode != EditPosition) {
6811         ChessSquare fromP;
6812         ChessSquare toP;
6813         int frc;
6814
6815         // ignore off-board to clicks
6816         if(y < 0 || x < 0) return;
6817
6818         /* Check if clicking again on the same color piece */
6819         fromP = boards[currentMove][fromY][fromX];
6820         toP = boards[currentMove][y][x];
6821         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6822         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6823              WhitePawn <= toP && toP <= WhiteKing &&
6824              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6825              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6826             (BlackPawn <= fromP && fromP <= BlackKing &&
6827              BlackPawn <= toP && toP <= BlackKing &&
6828              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6829              !(fromP == BlackKing && toP == BlackRook && frc))) {
6830             /* Clicked again on same color piece -- changed his mind */
6831             second = (x == fromX && y == fromY);
6832             promoDefaultAltered = FALSE;
6833            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6834             if (appData.highlightDragging) {
6835                 SetHighlights(x, y, -1, -1);
6836             } else {
6837                 ClearHighlights();
6838             }
6839             if (OKToStartUserMove(x, y)) {
6840                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6841                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6842                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6843                  gatingPiece = boards[currentMove][fromY][fromX];
6844                 else gatingPiece = EmptySquare;
6845                 fromX = x;
6846                 fromY = y; dragging = 1;
6847                 MarkTargetSquares(0);
6848                 DragPieceBegin(xPix, yPix);
6849                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6850                     promoSweep = defaultPromoChoice;
6851                     selectFlag = 0; lastX = xPix; lastY = yPix;
6852                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6853                 }
6854             }
6855            }
6856            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6857            second = FALSE; 
6858         }
6859         // ignore clicks on holdings
6860         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6861     }
6862
6863     if (clickType == Release && x == fromX && y == fromY) {
6864         DragPieceEnd(xPix, yPix); dragging = 0;
6865         if(clearFlag) {
6866             // a deferred attempt to click-click move an empty square on top of a piece
6867             boards[currentMove][y][x] = EmptySquare;
6868             ClearHighlights();
6869             DrawPosition(FALSE, boards[currentMove]);
6870             fromX = fromY = -1; clearFlag = 0;
6871             return;
6872         }
6873         if (appData.animateDragging) {
6874             /* Undo animation damage if any */
6875             DrawPosition(FALSE, NULL);
6876         }
6877         if (second) {
6878             /* Second up/down in same square; just abort move */
6879             second = 0;
6880             fromX = fromY = -1;
6881             gatingPiece = EmptySquare;
6882             ClearHighlights();
6883             gotPremove = 0;
6884             ClearPremoveHighlights();
6885         } else {
6886             /* First upclick in same square; start click-click mode */
6887             SetHighlights(x, y, -1, -1);
6888         }
6889         return;
6890     }
6891
6892     clearFlag = 0;
6893
6894     /* we now have a different from- and (possibly off-board) to-square */
6895     /* Completed move */
6896     toX = x;
6897     toY = y;
6898     saveAnimate = appData.animate;
6899     if (clickType == Press) {
6900         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6901             // must be Edit Position mode with empty-square selected
6902             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6903             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6904             return;
6905         }
6906         /* Finish clickclick move */
6907         if (appData.animate || appData.highlightLastMove) {
6908             SetHighlights(fromX, fromY, toX, toY);
6909         } else {
6910             ClearHighlights();
6911         }
6912     } else {
6913         /* Finish drag move */
6914         if (appData.highlightLastMove) {
6915             SetHighlights(fromX, fromY, toX, toY);
6916         } else {
6917             ClearHighlights();
6918         }
6919         DragPieceEnd(xPix, yPix); dragging = 0;
6920         /* Don't animate move and drag both */
6921         appData.animate = FALSE;
6922     }
6923
6924     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6925     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6926         ChessSquare piece = boards[currentMove][fromY][fromX];
6927         if(gameMode == EditPosition && piece != EmptySquare &&
6928            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6929             int n;
6930
6931             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6932                 n = PieceToNumber(piece - (int)BlackPawn);
6933                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6934                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6935                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6936             } else
6937             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6938                 n = PieceToNumber(piece);
6939                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6940                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6941                 boards[currentMove][n][BOARD_WIDTH-2]++;
6942             }
6943             boards[currentMove][fromY][fromX] = EmptySquare;
6944         }
6945         ClearHighlights();
6946         fromX = fromY = -1;
6947         DrawPosition(TRUE, boards[currentMove]);
6948         return;
6949     }
6950
6951     // off-board moves should not be highlighted
6952     if(x < 0 || y < 0) ClearHighlights();
6953
6954     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6955
6956     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6957         SetHighlights(fromX, fromY, toX, toY);
6958         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6959             // [HGM] super: promotion to captured piece selected from holdings
6960             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6961             promotionChoice = TRUE;
6962             // kludge follows to temporarily execute move on display, without promoting yet
6963             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6964             boards[currentMove][toY][toX] = p;
6965             DrawPosition(FALSE, boards[currentMove]);
6966             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6967             boards[currentMove][toY][toX] = q;
6968             DisplayMessage("Click in holdings to choose piece", "");
6969             return;
6970         }
6971         PromotionPopUp();
6972     } else {
6973         int oldMove = currentMove;
6974         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6975         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6976         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6977         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6978            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6979             DrawPosition(TRUE, boards[currentMove]);
6980         fromX = fromY = -1;
6981     }
6982     appData.animate = saveAnimate;
6983     if (appData.animate || appData.animateDragging) {
6984         /* Undo animation damage if needed */
6985         DrawPosition(FALSE, NULL);
6986     }
6987 }
6988
6989 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6990 {   // front-end-free part taken out of PieceMenuPopup
6991     int whichMenu; int xSqr, ySqr;
6992
6993     if(seekGraphUp) { // [HGM] seekgraph
6994         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6995         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6996         return -2;
6997     }
6998
6999     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7000          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7001         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7002         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7003         if(action == Press)   {
7004             originalFlip = flipView;
7005             flipView = !flipView; // temporarily flip board to see game from partners perspective
7006             DrawPosition(TRUE, partnerBoard);
7007             DisplayMessage(partnerStatus, "");
7008             partnerUp = TRUE;
7009         } else if(action == Release) {
7010             flipView = originalFlip;
7011             DrawPosition(TRUE, boards[currentMove]);
7012             partnerUp = FALSE;
7013         }
7014         return -2;
7015     }
7016
7017     xSqr = EventToSquare(x, BOARD_WIDTH);
7018     ySqr = EventToSquare(y, BOARD_HEIGHT);
7019     if (action == Release) {
7020         if(pieceSweep != EmptySquare) {
7021             EditPositionMenuEvent(pieceSweep, toX, toY);
7022             pieceSweep = EmptySquare;
7023         } else UnLoadPV(); // [HGM] pv
7024     }
7025     if (action != Press) return -2; // return code to be ignored
7026     switch (gameMode) {
7027       case IcsExamining:
7028         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7029       case EditPosition:
7030         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7031         if (xSqr < 0 || ySqr < 0) return -1;
7032         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7033         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7034         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7035         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7036         NextPiece(0);
7037         return -2;\r
7038       case IcsObserving:
7039         if(!appData.icsEngineAnalyze) return -1;
7040       case IcsPlayingWhite:
7041       case IcsPlayingBlack:
7042         if(!appData.zippyPlay) goto noZip;
7043       case AnalyzeMode:
7044       case AnalyzeFile:
7045       case MachinePlaysWhite:
7046       case MachinePlaysBlack:
7047       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7048         if (!appData.dropMenu) {
7049           LoadPV(x, y);
7050           return 2; // flag front-end to grab mouse events
7051         }
7052         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7053            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7054       case EditGame:
7055       noZip:
7056         if (xSqr < 0 || ySqr < 0) return -1;
7057         if (!appData.dropMenu || appData.testLegality &&
7058             gameInfo.variant != VariantBughouse &&
7059             gameInfo.variant != VariantCrazyhouse) return -1;
7060         whichMenu = 1; // drop menu
7061         break;
7062       default:
7063         return -1;
7064     }
7065
7066     if (((*fromX = xSqr) < 0) ||
7067         ((*fromY = ySqr) < 0)) {
7068         *fromX = *fromY = -1;
7069         return -1;
7070     }
7071     if (flipView)
7072       *fromX = BOARD_WIDTH - 1 - *fromX;
7073     else
7074       *fromY = BOARD_HEIGHT - 1 - *fromY;
7075
7076     return whichMenu;
7077 }
7078
7079 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7080 {
7081 //    char * hint = lastHint;
7082     FrontEndProgramStats stats;
7083
7084     stats.which = cps == &first ? 0 : 1;
7085     stats.depth = cpstats->depth;
7086     stats.nodes = cpstats->nodes;
7087     stats.score = cpstats->score;
7088     stats.time = cpstats->time;
7089     stats.pv = cpstats->movelist;
7090     stats.hint = lastHint;
7091     stats.an_move_index = 0;
7092     stats.an_move_count = 0;
7093
7094     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7095         stats.hint = cpstats->move_name;
7096         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7097         stats.an_move_count = cpstats->nr_moves;
7098     }
7099
7100     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
7101
7102     SetProgramStats( &stats );
7103 }
7104
7105 #define MAXPLAYERS 500
7106
7107 char *
7108 TourneyStandings(int display)
7109 {
7110     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7111     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7112     char result, *p, *names[MAXPLAYERS];
7113
7114     names[0] = p = strdup(appData.participants);
7115     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7116
7117     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7118
7119     while(result = appData.results[nr]) {
7120         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7121         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7122         wScore = bScore = 0;
7123         switch(result) {
7124           case '+': wScore = 2; break;
7125           case '-': bScore = 2; break;
7126           case '=': wScore = bScore = 1; break;
7127           case ' ':
7128           case '*': return strdup("busy"); // tourney not finished
7129         }
7130         score[w] += wScore;
7131         score[b] += bScore;
7132         games[w]++;
7133         games[b]++;
7134         nr++;
7135     }
7136     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7137     for(w=0; w<nPlayers; w++) {
7138         bScore = -1;
7139         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7140         ranking[w] = b; points[w] = bScore; score[b] = -2;
7141     }
7142     p = malloc(nPlayers*34+1);
7143     for(w=0; w<nPlayers && w<display; w++)
7144         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7145     free(names[0]);
7146     return p;
7147 }
7148
7149 void
7150 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7151 {       // count all piece types
7152         int p, f, r;
7153         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7154         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7155         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7156                 p = board[r][f];
7157                 pCnt[p]++;
7158                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7159                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7160                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7161                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7162                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7163                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7164         }
7165 }
7166
7167 int
7168 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7169 {
7170         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7171         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7172
7173         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7174         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7175         if(myPawns == 2 && nMine == 3) // KPP
7176             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7177         if(myPawns == 1 && nMine == 2) // KP
7178             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7179         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7180             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7181         if(myPawns) return FALSE;
7182         if(pCnt[WhiteRook+side])
7183             return pCnt[BlackRook-side] ||
7184                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7185                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7186                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7187         if(pCnt[WhiteCannon+side]) {
7188             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7189             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7190         }
7191         if(pCnt[WhiteKnight+side])
7192             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7193         return FALSE;
7194 }
7195
7196 int
7197 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7198 {
7199         VariantClass v = gameInfo.variant;
7200
7201         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7202         if(v == VariantShatranj) return TRUE; // always winnable through baring
7203         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7204         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7205
7206         if(v == VariantXiangqi) {
7207                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7208
7209                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7210                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7211                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7212                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7213                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7214                 if(stale) // we have at least one last-rank P plus perhaps C
7215                     return majors // KPKX
7216                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7217                 else // KCA*E*
7218                     return pCnt[WhiteFerz+side] // KCAK
7219                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7220                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7221                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7222
7223         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7224                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7225
7226                 if(nMine == 1) return FALSE; // bare King
7227                 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
7228                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7229                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7230                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7231                 if(pCnt[WhiteKnight+side])
7232                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7233                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7234                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7235                 if(nBishops)
7236                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7237                 if(pCnt[WhiteAlfil+side])
7238                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7239                 if(pCnt[WhiteWazir+side])
7240                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7241         }
7242
7243         return TRUE;
7244 }
7245
7246 int
7247 Adjudicate(ChessProgramState *cps)
7248 {       // [HGM] some adjudications useful with buggy engines
7249         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7250         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7251         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7252         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7253         int k, count = 0; static int bare = 1;
7254         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7255         Boolean canAdjudicate = !appData.icsActive;
7256
7257         // most tests only when we understand the game, i.e. legality-checking on
7258             if( appData.testLegality )
7259             {   /* [HGM] Some more adjudications for obstinate engines */
7260                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7261                 static int moveCount = 6;
7262                 ChessMove result;
7263                 char *reason = NULL;
7264
7265                 /* Count what is on board. */
7266                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7267
7268                 /* Some material-based adjudications that have to be made before stalemate test */
7269                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7270                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7271                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7272                      if(canAdjudicate && appData.checkMates) {
7273                          if(engineOpponent)
7274                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7275                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7276                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7277                          return 1;
7278                      }
7279                 }
7280
7281                 /* Bare King in Shatranj (loses) or Losers (wins) */
7282                 if( nrW == 1 || nrB == 1) {
7283                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7284                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7285                      if(canAdjudicate && appData.checkMates) {
7286                          if(engineOpponent)
7287                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7288                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7289                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7290                          return 1;
7291                      }
7292                   } else
7293                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7294                   {    /* bare King */
7295                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7296                         if(canAdjudicate && appData.checkMates) {
7297                             /* but only adjudicate if adjudication enabled */
7298                             if(engineOpponent)
7299                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7300                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7301                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7302                             return 1;
7303                         }
7304                   }
7305                 } else bare = 1;
7306
7307
7308             // don't wait for engine to announce game end if we can judge ourselves
7309             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7310               case MT_CHECK:
7311                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7312                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7313                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7314                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7315                             checkCnt++;
7316                         if(checkCnt >= 2) {
7317                             reason = "Xboard adjudication: 3rd check";
7318                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7319                             break;
7320                         }
7321                     }
7322                 }
7323               case MT_NONE:
7324               default:
7325                 break;
7326               case MT_STALEMATE:
7327               case MT_STAINMATE:
7328                 reason = "Xboard adjudication: Stalemate";
7329                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7330                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7331                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7332                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7333                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7334                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7335                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7336                                                                         EP_CHECKMATE : EP_WINS);
7337                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7338                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7339                 }
7340                 break;
7341               case MT_CHECKMATE:
7342                 reason = "Xboard adjudication: Checkmate";
7343                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7344                 break;
7345             }
7346
7347                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7348                     case EP_STALEMATE:
7349                         result = GameIsDrawn; break;
7350                     case EP_CHECKMATE:
7351                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7352                     case EP_WINS:
7353                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7354                     default:
7355                         result = EndOfFile;
7356                 }
7357                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7358                     if(engineOpponent)
7359                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7360                     GameEnds( result, reason, GE_XBOARD );
7361                     return 1;
7362                 }
7363
7364                 /* Next absolutely insufficient mating material. */
7365                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7366                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7367                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7368
7369                      /* always flag draws, for judging claims */
7370                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7371
7372                      if(canAdjudicate && appData.materialDraws) {
7373                          /* but only adjudicate them if adjudication enabled */
7374                          if(engineOpponent) {
7375                            SendToProgram("force\n", engineOpponent); // suppress reply
7376                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7377                          }
7378                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7379                          return 1;
7380                      }
7381                 }
7382
7383                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7384                 if(gameInfo.variant == VariantXiangqi ?
7385                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7386                  : nrW + nrB == 4 &&
7387                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7388                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7389                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7390                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7391                    ) ) {
7392                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7393                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7394                           if(engineOpponent) {
7395                             SendToProgram("force\n", engineOpponent); // suppress reply
7396                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7397                           }
7398                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7399                           return 1;
7400                      }
7401                 } else moveCount = 6;
7402             }
7403         if (appData.debugMode) { int i;
7404             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7405                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7406                     appData.drawRepeats);
7407             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7408               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7409
7410         }
7411
7412         // Repetition draws and 50-move rule can be applied independently of legality testing
7413
7414                 /* Check for rep-draws */
7415                 count = 0;
7416                 for(k = forwardMostMove-2;
7417                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7418                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7419                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7420                     k-=2)
7421                 {   int rights=0;
7422                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7423                         /* compare castling rights */
7424                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7425                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7426                                 rights++; /* King lost rights, while rook still had them */
7427                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7428                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7429                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7430                                    rights++; /* but at least one rook lost them */
7431                         }
7432                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7433                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7434                                 rights++;
7435                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7436                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7437                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7438                                    rights++;
7439                         }
7440                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7441                             && appData.drawRepeats > 1) {
7442                              /* adjudicate after user-specified nr of repeats */
7443                              int result = GameIsDrawn;
7444                              char *details = "XBoard adjudication: repetition draw";
7445                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7446                                 // [HGM] xiangqi: check for forbidden perpetuals
7447                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7448                                 for(m=forwardMostMove; m>k; m-=2) {
7449                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7450                                         ourPerpetual = 0; // the current mover did not always check
7451                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7452                                         hisPerpetual = 0; // the opponent did not always check
7453                                 }
7454                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7455                                                                         ourPerpetual, hisPerpetual);
7456                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7457                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7458                                     details = "Xboard adjudication: perpetual checking";
7459                                 } else
7460                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7461                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7462                                 } else
7463                                 // Now check for perpetual chases
7464                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7465                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7466                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7467                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7468                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7469                                         details = "Xboard adjudication: perpetual chasing";
7470                                     } else
7471                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7472                                         break; // Abort repetition-checking loop.
7473                                 }
7474                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7475                              }
7476                              if(engineOpponent) {
7477                                SendToProgram("force\n", engineOpponent); // suppress reply
7478                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7479                              }
7480                              GameEnds( result, details, GE_XBOARD );
7481                              return 1;
7482                         }
7483                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7484                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7485                     }
7486                 }
7487
7488                 /* Now we test for 50-move draws. Determine ply count */
7489                 count = forwardMostMove;
7490                 /* look for last irreversble move */
7491                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7492                     count--;
7493                 /* if we hit starting position, add initial plies */
7494                 if( count == backwardMostMove )
7495                     count -= initialRulePlies;
7496                 count = forwardMostMove - count;
7497                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7498                         // adjust reversible move counter for checks in Xiangqi
7499                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7500                         if(i < backwardMostMove) i = backwardMostMove;
7501                         while(i <= forwardMostMove) {
7502                                 lastCheck = inCheck; // check evasion does not count
7503                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7504                                 if(inCheck || lastCheck) count--; // check does not count
7505                                 i++;
7506                         }
7507                 }
7508                 if( count >= 100)
7509                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7510                          /* this is used to judge if draw claims are legal */
7511                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7512                          if(engineOpponent) {
7513                            SendToProgram("force\n", engineOpponent); // suppress reply
7514                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7515                          }
7516                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7517                          return 1;
7518                 }
7519
7520                 /* if draw offer is pending, treat it as a draw claim
7521                  * when draw condition present, to allow engines a way to
7522                  * claim draws before making their move to avoid a race
7523                  * condition occurring after their move
7524                  */
7525                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7526                          char *p = NULL;
7527                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7528                              p = "Draw claim: 50-move rule";
7529                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7530                              p = "Draw claim: 3-fold repetition";
7531                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7532                              p = "Draw claim: insufficient mating material";
7533                          if( p != NULL && canAdjudicate) {
7534                              if(engineOpponent) {
7535                                SendToProgram("force\n", engineOpponent); // suppress reply
7536                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7537                              }
7538                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7539                              return 1;
7540                          }
7541                 }
7542
7543                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7544                     if(engineOpponent) {
7545                       SendToProgram("force\n", engineOpponent); // suppress reply
7546                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7547                     }
7548                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7549                     return 1;
7550                 }
7551         return 0;
7552 }
7553
7554 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7555 {   // [HGM] book: this routine intercepts moves to simulate book replies
7556     char *bookHit = NULL;
7557
7558     //first determine if the incoming move brings opponent into his book
7559     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7560         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7561     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7562     if(bookHit != NULL && !cps->bookSuspend) {
7563         // make sure opponent is not going to reply after receiving move to book position
7564         SendToProgram("force\n", cps);
7565         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7566     }
7567     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7568     // now arrange restart after book miss
7569     if(bookHit) {
7570         // after a book hit we never send 'go', and the code after the call to this routine
7571         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7572         char buf[MSG_SIZ];
7573         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7574         SendToProgram(buf, cps);
7575         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7576     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7577         SendToProgram("go\n", cps);
7578         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7579     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7580         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7581             SendToProgram("go\n", cps);
7582         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7583     }
7584     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7585 }
7586
7587 char *savedMessage;
7588 ChessProgramState *savedState;
7589 void DeferredBookMove(void)
7590 {
7591         if(savedState->lastPing != savedState->lastPong)
7592                     ScheduleDelayedEvent(DeferredBookMove, 10);
7593         else
7594         HandleMachineMove(savedMessage, savedState);
7595 }
7596
7597 void
7598 HandleMachineMove(message, cps)
7599      char *message;
7600      ChessProgramState *cps;
7601 {
7602     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7603     char realname[MSG_SIZ];
7604     int fromX, fromY, toX, toY;
7605     ChessMove moveType;
7606     char promoChar;
7607     char *p;
7608     int machineWhite;
7609     char *bookHit;
7610
7611     cps->userError = 0;
7612
7613 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7614     /*
7615      * Kludge to ignore BEL characters
7616      */
7617     while (*message == '\007') message++;
7618
7619     /*
7620      * [HGM] engine debug message: ignore lines starting with '#' character
7621      */
7622     if(cps->debug && *message == '#') return;
7623
7624     /*
7625      * Look for book output
7626      */
7627     if (cps == &first && bookRequested) {
7628         if (message[0] == '\t' || message[0] == ' ') {
7629             /* Part of the book output is here; append it */
7630             strcat(bookOutput, message);
7631             strcat(bookOutput, "  \n");
7632             return;
7633         } else if (bookOutput[0] != NULLCHAR) {
7634             /* All of book output has arrived; display it */
7635             char *p = bookOutput;
7636             while (*p != NULLCHAR) {
7637                 if (*p == '\t') *p = ' ';
7638                 p++;
7639             }
7640             DisplayInformation(bookOutput);
7641             bookRequested = FALSE;
7642             /* Fall through to parse the current output */
7643         }
7644     }
7645
7646     /*
7647      * Look for machine move.
7648      */
7649     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7650         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7651     {
7652         /* This method is only useful on engines that support ping */
7653         if (cps->lastPing != cps->lastPong) {
7654           if (gameMode == BeginningOfGame) {
7655             /* Extra move from before last new; ignore */
7656             if (appData.debugMode) {
7657                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7658             }
7659           } else {
7660             if (appData.debugMode) {
7661                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7662                         cps->which, gameMode);
7663             }
7664
7665             SendToProgram("undo\n", cps);
7666           }
7667           return;
7668         }
7669
7670         switch (gameMode) {
7671           case BeginningOfGame:
7672             /* Extra move from before last reset; ignore */
7673             if (appData.debugMode) {
7674                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7675             }
7676             return;
7677
7678           case EndOfGame:
7679           case IcsIdle:
7680           default:
7681             /* Extra move after we tried to stop.  The mode test is
7682                not a reliable way of detecting this problem, but it's
7683                the best we can do on engines that don't support ping.
7684             */
7685             if (appData.debugMode) {
7686                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7687                         cps->which, gameMode);
7688             }
7689             SendToProgram("undo\n", cps);
7690             return;
7691
7692           case MachinePlaysWhite:
7693           case IcsPlayingWhite:
7694             machineWhite = TRUE;
7695             break;
7696
7697           case MachinePlaysBlack:
7698           case IcsPlayingBlack:
7699             machineWhite = FALSE;
7700             break;
7701
7702           case TwoMachinesPlay:
7703             machineWhite = (cps->twoMachinesColor[0] == 'w');
7704             break;
7705         }
7706         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7707             if (appData.debugMode) {
7708                 fprintf(debugFP,
7709                         "Ignoring move out of turn by %s, gameMode %d"
7710                         ", forwardMost %d\n",
7711                         cps->which, gameMode, forwardMostMove);
7712             }
7713             return;
7714         }
7715
7716     if (appData.debugMode) { int f = forwardMostMove;
7717         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7718                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7719                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7720     }
7721         if(cps->alphaRank) AlphaRank(machineMove, 4);
7722         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7723                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7724             /* Machine move could not be parsed; ignore it. */
7725           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7726                     machineMove, _(cps->which));
7727             DisplayError(buf1, 0);
7728             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7729                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7730             if (gameMode == TwoMachinesPlay) {
7731               GameEnds(machineWhite ? BlackWins : WhiteWins,
7732                        buf1, GE_XBOARD);
7733             }
7734             return;
7735         }
7736
7737         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7738         /* So we have to redo legality test with true e.p. status here,  */
7739         /* to make sure an illegal e.p. capture does not slip through,   */
7740         /* to cause a forfeit on a justified illegal-move complaint      */
7741         /* of the opponent.                                              */
7742         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7743            ChessMove moveType;
7744            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7745                              fromY, fromX, toY, toX, promoChar);
7746             if (appData.debugMode) {
7747                 int i;
7748                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7749                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7750                 fprintf(debugFP, "castling rights\n");
7751             }
7752             if(moveType == IllegalMove) {
7753               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7754                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7755                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7756                            buf1, GE_XBOARD);
7757                 return;
7758            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7759            /* [HGM] Kludge to handle engines that send FRC-style castling
7760               when they shouldn't (like TSCP-Gothic) */
7761            switch(moveType) {
7762              case WhiteASideCastleFR:
7763              case BlackASideCastleFR:
7764                toX+=2;
7765                currentMoveString[2]++;
7766                break;
7767              case WhiteHSideCastleFR:
7768              case BlackHSideCastleFR:
7769                toX--;
7770                currentMoveString[2]--;
7771                break;
7772              default: ; // nothing to do, but suppresses warning of pedantic compilers
7773            }
7774         }
7775         hintRequested = FALSE;
7776         lastHint[0] = NULLCHAR;
7777         bookRequested = FALSE;
7778         /* Program may be pondering now */
7779         cps->maybeThinking = TRUE;
7780         if (cps->sendTime == 2) cps->sendTime = 1;
7781         if (cps->offeredDraw) cps->offeredDraw--;
7782
7783         /* [AS] Save move info*/
7784         pvInfoList[ forwardMostMove ].score = programStats.score;
7785         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7786         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7787
7788         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7789
7790         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7791         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7792             int count = 0;
7793
7794             while( count < adjudicateLossPlies ) {
7795                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7796
7797                 if( count & 1 ) {
7798                     score = -score; /* Flip score for winning side */
7799                 }
7800
7801                 if( score > adjudicateLossThreshold ) {
7802                     break;
7803                 }
7804
7805                 count++;
7806             }
7807
7808             if( count >= adjudicateLossPlies ) {
7809                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7810
7811                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7812                     "Xboard adjudication",
7813                     GE_XBOARD );
7814
7815                 return;
7816             }
7817         }
7818
7819         if(Adjudicate(cps)) {
7820             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7821             return; // [HGM] adjudicate: for all automatic game ends
7822         }
7823
7824 #if ZIPPY
7825         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7826             first.initDone) {
7827           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7828                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7829                 SendToICS("draw ");
7830                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7831           }
7832           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7833           ics_user_moved = 1;
7834           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7835                 char buf[3*MSG_SIZ];
7836
7837                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7838                         programStats.score / 100.,
7839                         programStats.depth,
7840                         programStats.time / 100.,
7841                         (unsigned int)programStats.nodes,
7842                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7843                         programStats.movelist);
7844                 SendToICS(buf);
7845 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7846           }
7847         }
7848 #endif
7849
7850         /* [AS] Clear stats for next move */
7851         ClearProgramStats();
7852         thinkOutput[0] = NULLCHAR;
7853         hiddenThinkOutputState = 0;
7854
7855         bookHit = NULL;
7856         if (gameMode == TwoMachinesPlay) {
7857             /* [HGM] relaying draw offers moved to after reception of move */
7858             /* and interpreting offer as claim if it brings draw condition */
7859             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7860                 SendToProgram("draw\n", cps->other);
7861             }
7862             if (cps->other->sendTime) {
7863                 SendTimeRemaining(cps->other,
7864                                   cps->other->twoMachinesColor[0] == 'w');
7865             }
7866             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7867             if (firstMove && !bookHit) {
7868                 firstMove = FALSE;
7869                 if (cps->other->useColors) {
7870                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7871                 }
7872                 SendToProgram("go\n", cps->other);
7873             }
7874             cps->other->maybeThinking = TRUE;
7875         }
7876
7877         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7878
7879         if (!pausing && appData.ringBellAfterMoves) {
7880             RingBell();
7881         }
7882
7883         /*
7884          * Reenable menu items that were disabled while
7885          * machine was thinking
7886          */
7887         if (gameMode != TwoMachinesPlay)
7888             SetUserThinkingEnables();
7889
7890         // [HGM] book: after book hit opponent has received move and is now in force mode
7891         // force the book reply into it, and then fake that it outputted this move by jumping
7892         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7893         if(bookHit) {
7894                 static char bookMove[MSG_SIZ]; // a bit generous?
7895
7896                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7897                 strcat(bookMove, bookHit);
7898                 message = bookMove;
7899                 cps = cps->other;
7900                 programStats.nodes = programStats.depth = programStats.time =
7901                 programStats.score = programStats.got_only_move = 0;
7902                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7903
7904                 if(cps->lastPing != cps->lastPong) {
7905                     savedMessage = message; // args for deferred call
7906                     savedState = cps;
7907                     ScheduleDelayedEvent(DeferredBookMove, 10);
7908                     return;
7909                 }
7910                 goto FakeBookMove;
7911         }
7912
7913         return;
7914     }
7915
7916     /* Set special modes for chess engines.  Later something general
7917      *  could be added here; for now there is just one kludge feature,
7918      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7919      *  when "xboard" is given as an interactive command.
7920      */
7921     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7922         cps->useSigint = FALSE;
7923         cps->useSigterm = FALSE;
7924     }
7925     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7926       ParseFeatures(message+8, cps);
7927       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7928     }
7929
7930     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7931       int dummy, s=6; char buf[MSG_SIZ];
7932       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7933       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7934       ParseFEN(boards[0], &dummy, message+s);
7935       DrawPosition(TRUE, boards[0]);
7936       startedFromSetupPosition = TRUE;
7937       return;
7938     }
7939     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7940      * want this, I was asked to put it in, and obliged.
7941      */
7942     if (!strncmp(message, "setboard ", 9)) {
7943         Board initial_position;
7944
7945         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7946
7947         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7948             DisplayError(_("Bad FEN received from engine"), 0);
7949             return ;
7950         } else {
7951            Reset(TRUE, FALSE);
7952            CopyBoard(boards[0], initial_position);
7953            initialRulePlies = FENrulePlies;
7954            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7955            else gameMode = MachinePlaysBlack;
7956            DrawPosition(FALSE, boards[currentMove]);
7957         }
7958         return;
7959     }
7960
7961     /*
7962      * Look for communication commands
7963      */
7964     if (!strncmp(message, "telluser ", 9)) {
7965         if(message[9] == '\\' && message[10] == '\\')
7966             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7967         DisplayNote(message + 9);
7968         return;
7969     }
7970     if (!strncmp(message, "tellusererror ", 14)) {
7971         cps->userError = 1;
7972         if(message[14] == '\\' && message[15] == '\\')
7973             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7974         DisplayError(message + 14, 0);
7975         return;
7976     }
7977     if (!strncmp(message, "tellopponent ", 13)) {
7978       if (appData.icsActive) {
7979         if (loggedOn) {
7980           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7981           SendToICS(buf1);
7982         }
7983       } else {
7984         DisplayNote(message + 13);
7985       }
7986       return;
7987     }
7988     if (!strncmp(message, "tellothers ", 11)) {
7989       if (appData.icsActive) {
7990         if (loggedOn) {
7991           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7992           SendToICS(buf1);
7993         }
7994       }
7995       return;
7996     }
7997     if (!strncmp(message, "tellall ", 8)) {
7998       if (appData.icsActive) {
7999         if (loggedOn) {
8000           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8001           SendToICS(buf1);
8002         }
8003       } else {
8004         DisplayNote(message + 8);
8005       }
8006       return;
8007     }
8008     if (strncmp(message, "warning", 7) == 0) {
8009         /* Undocumented feature, use tellusererror in new code */
8010         DisplayError(message, 0);
8011         return;
8012     }
8013     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8014         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8015         strcat(realname, " query");
8016         AskQuestion(realname, buf2, buf1, cps->pr);
8017         return;
8018     }
8019     /* Commands from the engine directly to ICS.  We don't allow these to be
8020      *  sent until we are logged on. Crafty kibitzes have been known to
8021      *  interfere with the login process.
8022      */
8023     if (loggedOn) {
8024         if (!strncmp(message, "tellics ", 8)) {
8025             SendToICS(message + 8);
8026             SendToICS("\n");
8027             return;
8028         }
8029         if (!strncmp(message, "tellicsnoalias ", 15)) {
8030             SendToICS(ics_prefix);
8031             SendToICS(message + 15);
8032             SendToICS("\n");
8033             return;
8034         }
8035         /* The following are for backward compatibility only */
8036         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8037             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8038             SendToICS(ics_prefix);
8039             SendToICS(message);
8040             SendToICS("\n");
8041             return;
8042         }
8043     }
8044     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8045         return;
8046     }
8047     /*
8048      * If the move is illegal, cancel it and redraw the board.
8049      * Also deal with other error cases.  Matching is rather loose
8050      * here to accommodate engines written before the spec.
8051      */
8052     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8053         strncmp(message, "Error", 5) == 0) {
8054         if (StrStr(message, "name") ||
8055             StrStr(message, "rating") || StrStr(message, "?") ||
8056             StrStr(message, "result") || StrStr(message, "board") ||
8057             StrStr(message, "bk") || StrStr(message, "computer") ||
8058             StrStr(message, "variant") || StrStr(message, "hint") ||
8059             StrStr(message, "random") || StrStr(message, "depth") ||
8060             StrStr(message, "accepted")) {
8061             return;
8062         }
8063         if (StrStr(message, "protover")) {
8064           /* Program is responding to input, so it's apparently done
8065              initializing, and this error message indicates it is
8066              protocol version 1.  So we don't need to wait any longer
8067              for it to initialize and send feature commands. */
8068           FeatureDone(cps, 1);
8069           cps->protocolVersion = 1;
8070           return;
8071         }
8072         cps->maybeThinking = FALSE;
8073
8074         if (StrStr(message, "draw")) {
8075             /* Program doesn't have "draw" command */
8076             cps->sendDrawOffers = 0;
8077             return;
8078         }
8079         if (cps->sendTime != 1 &&
8080             (StrStr(message, "time") || StrStr(message, "otim"))) {
8081           /* Program apparently doesn't have "time" or "otim" command */
8082           cps->sendTime = 0;
8083           return;
8084         }
8085         if (StrStr(message, "analyze")) {
8086             cps->analysisSupport = FALSE;
8087             cps->analyzing = FALSE;
8088             Reset(FALSE, TRUE);
8089             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8090             DisplayError(buf2, 0);
8091             return;
8092         }
8093         if (StrStr(message, "(no matching move)st")) {
8094           /* Special kludge for GNU Chess 4 only */
8095           cps->stKludge = TRUE;
8096           SendTimeControl(cps, movesPerSession, timeControl,
8097                           timeIncrement, appData.searchDepth,
8098                           searchTime);
8099           return;
8100         }
8101         if (StrStr(message, "(no matching move)sd")) {
8102           /* Special kludge for GNU Chess 4 only */
8103           cps->sdKludge = TRUE;
8104           SendTimeControl(cps, movesPerSession, timeControl,
8105                           timeIncrement, appData.searchDepth,
8106                           searchTime);
8107           return;
8108         }
8109         if (!StrStr(message, "llegal")) {
8110             return;
8111         }
8112         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8113             gameMode == IcsIdle) return;
8114         if (forwardMostMove <= backwardMostMove) return;
8115         if (pausing) PauseEvent();
8116       if(appData.forceIllegal) {
8117             // [HGM] illegal: machine refused move; force position after move into it
8118           SendToProgram("force\n", cps);
8119           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8120                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8121                 // when black is to move, while there might be nothing on a2 or black
8122                 // might already have the move. So send the board as if white has the move.
8123                 // But first we must change the stm of the engine, as it refused the last move
8124                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8125                 if(WhiteOnMove(forwardMostMove)) {
8126                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8127                     SendBoard(cps, forwardMostMove); // kludgeless board
8128                 } else {
8129                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8130                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8131                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8132                 }
8133           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8134             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8135                  gameMode == TwoMachinesPlay)
8136               SendToProgram("go\n", cps);
8137             return;
8138       } else
8139         if (gameMode == PlayFromGameFile) {
8140             /* Stop reading this game file */
8141             gameMode = EditGame;
8142             ModeHighlight();
8143         }
8144         /* [HGM] illegal-move claim should forfeit game when Xboard */
8145         /* only passes fully legal moves                            */
8146         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8147             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8148                                 "False illegal-move claim", GE_XBOARD );
8149             return; // do not take back move we tested as valid
8150         }
8151         currentMove = forwardMostMove-1;
8152         DisplayMove(currentMove-1); /* before DisplayMoveError */
8153         SwitchClocks(forwardMostMove-1); // [HGM] race
8154         DisplayBothClocks();
8155         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8156                 parseList[currentMove], _(cps->which));
8157         DisplayMoveError(buf1);
8158         DrawPosition(FALSE, boards[currentMove]);
8159         return;
8160     }
8161     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8162         /* Program has a broken "time" command that
8163            outputs a string not ending in newline.
8164            Don't use it. */
8165         cps->sendTime = 0;
8166     }
8167
8168     /*
8169      * If chess program startup fails, exit with an error message.
8170      * Attempts to recover here are futile.
8171      */
8172     if ((StrStr(message, "unknown host") != NULL)
8173         || (StrStr(message, "No remote directory") != NULL)
8174         || (StrStr(message, "not found") != NULL)
8175         || (StrStr(message, "No such file") != NULL)
8176         || (StrStr(message, "can't alloc") != NULL)
8177         || (StrStr(message, "Permission denied") != NULL)) {
8178
8179         cps->maybeThinking = FALSE;
8180         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8181                 _(cps->which), cps->program, cps->host, message);
8182         RemoveInputSource(cps->isr);
8183         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8184             if(cps == &first) appData.noChessProgram = TRUE;
8185             DisplayError(buf1, 0);
8186         }
8187         return;
8188     }
8189
8190     /*
8191      * Look for hint output
8192      */
8193     if (sscanf(message, "Hint: %s", buf1) == 1) {
8194         if (cps == &first && hintRequested) {
8195             hintRequested = FALSE;
8196             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8197                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8198                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8199                                     PosFlags(forwardMostMove),
8200                                     fromY, fromX, toY, toX, promoChar, buf1);
8201                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8202                 DisplayInformation(buf2);
8203             } else {
8204                 /* Hint move could not be parsed!? */
8205               snprintf(buf2, sizeof(buf2),
8206                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8207                         buf1, _(cps->which));
8208                 DisplayError(buf2, 0);
8209             }
8210         } else {
8211           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8212         }
8213         return;
8214     }
8215
8216     /*
8217      * Ignore other messages if game is not in progress
8218      */
8219     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8220         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8221
8222     /*
8223      * look for win, lose, draw, or draw offer
8224      */
8225     if (strncmp(message, "1-0", 3) == 0) {
8226         char *p, *q, *r = "";
8227         p = strchr(message, '{');
8228         if (p) {
8229             q = strchr(p, '}');
8230             if (q) {
8231                 *q = NULLCHAR;
8232                 r = p + 1;
8233             }
8234         }
8235         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8236         return;
8237     } else if (strncmp(message, "0-1", 3) == 0) {
8238         char *p, *q, *r = "";
8239         p = strchr(message, '{');
8240         if (p) {
8241             q = strchr(p, '}');
8242             if (q) {
8243                 *q = NULLCHAR;
8244                 r = p + 1;
8245             }
8246         }
8247         /* Kludge for Arasan 4.1 bug */
8248         if (strcmp(r, "Black resigns") == 0) {
8249             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8250             return;
8251         }
8252         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8253         return;
8254     } else if (strncmp(message, "1/2", 3) == 0) {
8255         char *p, *q, *r = "";
8256         p = strchr(message, '{');
8257         if (p) {
8258             q = strchr(p, '}');
8259             if (q) {
8260                 *q = NULLCHAR;
8261                 r = p + 1;
8262             }
8263         }
8264
8265         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8266         return;
8267
8268     } else if (strncmp(message, "White resign", 12) == 0) {
8269         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8270         return;
8271     } else if (strncmp(message, "Black resign", 12) == 0) {
8272         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8273         return;
8274     } else if (strncmp(message, "White matches", 13) == 0 ||
8275                strncmp(message, "Black matches", 13) == 0   ) {
8276         /* [HGM] ignore GNUShogi noises */
8277         return;
8278     } else if (strncmp(message, "White", 5) == 0 &&
8279                message[5] != '(' &&
8280                StrStr(message, "Black") == NULL) {
8281         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8282         return;
8283     } else if (strncmp(message, "Black", 5) == 0 &&
8284                message[5] != '(') {
8285         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8286         return;
8287     } else if (strcmp(message, "resign") == 0 ||
8288                strcmp(message, "computer resigns") == 0) {
8289         switch (gameMode) {
8290           case MachinePlaysBlack:
8291           case IcsPlayingBlack:
8292             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8293             break;
8294           case MachinePlaysWhite:
8295           case IcsPlayingWhite:
8296             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8297             break;
8298           case TwoMachinesPlay:
8299             if (cps->twoMachinesColor[0] == 'w')
8300               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8301             else
8302               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8303             break;
8304           default:
8305             /* can't happen */
8306             break;
8307         }
8308         return;
8309     } else if (strncmp(message, "opponent mates", 14) == 0) {
8310         switch (gameMode) {
8311           case MachinePlaysBlack:
8312           case IcsPlayingBlack:
8313             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8314             break;
8315           case MachinePlaysWhite:
8316           case IcsPlayingWhite:
8317             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8318             break;
8319           case TwoMachinesPlay:
8320             if (cps->twoMachinesColor[0] == 'w')
8321               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8322             else
8323               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8324             break;
8325           default:
8326             /* can't happen */
8327             break;
8328         }
8329         return;
8330     } else if (strncmp(message, "computer mates", 14) == 0) {
8331         switch (gameMode) {
8332           case MachinePlaysBlack:
8333           case IcsPlayingBlack:
8334             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8335             break;
8336           case MachinePlaysWhite:
8337           case IcsPlayingWhite:
8338             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8339             break;
8340           case TwoMachinesPlay:
8341             if (cps->twoMachinesColor[0] == 'w')
8342               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8343             else
8344               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8345             break;
8346           default:
8347             /* can't happen */
8348             break;
8349         }
8350         return;
8351     } else if (strncmp(message, "checkmate", 9) == 0) {
8352         if (WhiteOnMove(forwardMostMove)) {
8353             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8354         } else {
8355             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8356         }
8357         return;
8358     } else if (strstr(message, "Draw") != NULL ||
8359                strstr(message, "game is a draw") != NULL) {
8360         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8361         return;
8362     } else if (strstr(message, "offer") != NULL &&
8363                strstr(message, "draw") != NULL) {
8364 #if ZIPPY
8365         if (appData.zippyPlay && first.initDone) {
8366             /* Relay offer to ICS */
8367             SendToICS(ics_prefix);
8368             SendToICS("draw\n");
8369         }
8370 #endif
8371         cps->offeredDraw = 2; /* valid until this engine moves twice */
8372         if (gameMode == TwoMachinesPlay) {
8373             if (cps->other->offeredDraw) {
8374                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8375             /* [HGM] in two-machine mode we delay relaying draw offer      */
8376             /* until after we also have move, to see if it is really claim */
8377             }
8378         } else if (gameMode == MachinePlaysWhite ||
8379                    gameMode == MachinePlaysBlack) {
8380           if (userOfferedDraw) {
8381             DisplayInformation(_("Machine accepts your draw offer"));
8382             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8383           } else {
8384             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8385           }
8386         }
8387     }
8388
8389
8390     /*
8391      * Look for thinking output
8392      */
8393     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8394           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8395                                 ) {
8396         int plylev, mvleft, mvtot, curscore, time;
8397         char mvname[MOVE_LEN];
8398         u64 nodes; // [DM]
8399         char plyext;
8400         int ignore = FALSE;
8401         int prefixHint = FALSE;
8402         mvname[0] = NULLCHAR;
8403
8404         switch (gameMode) {
8405           case MachinePlaysBlack:
8406           case IcsPlayingBlack:
8407             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8408             break;
8409           case MachinePlaysWhite:
8410           case IcsPlayingWhite:
8411             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8412             break;
8413           case AnalyzeMode:
8414           case AnalyzeFile:
8415             break;
8416           case IcsObserving: /* [DM] icsEngineAnalyze */
8417             if (!appData.icsEngineAnalyze) ignore = TRUE;
8418             break;
8419           case TwoMachinesPlay:
8420             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8421                 ignore = TRUE;
8422             }
8423             break;
8424           default:
8425             ignore = TRUE;
8426             break;
8427         }
8428
8429         if (!ignore) {
8430             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8431             buf1[0] = NULLCHAR;
8432             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8433                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8434
8435                 if (plyext != ' ' && plyext != '\t') {
8436                     time *= 100;
8437                 }
8438
8439                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8440                 if( cps->scoreIsAbsolute &&
8441                     ( gameMode == MachinePlaysBlack ||
8442                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8443                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8444                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8445                      !WhiteOnMove(currentMove)
8446                     ) )
8447                 {
8448                     curscore = -curscore;
8449                 }
8450
8451
8452                 tempStats.depth = plylev;
8453                 tempStats.nodes = nodes;
8454                 tempStats.time = time;
8455                 tempStats.score = curscore;
8456                 tempStats.got_only_move = 0;
8457
8458                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8459                         int ticklen;
8460
8461                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8462                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8463                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8464                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8465                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8466                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8467                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8468                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8469                 }
8470
8471                 /* Buffer overflow protection */
8472                 if (buf1[0] != NULLCHAR) {
8473                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8474                         && appData.debugMode) {
8475                         fprintf(debugFP,
8476                                 "PV is too long; using the first %u bytes.\n",
8477                                 (unsigned) sizeof(tempStats.movelist) - 1);
8478                     }
8479
8480                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8481                 } else {
8482                     sprintf(tempStats.movelist, " no PV\n");
8483                 }
8484
8485                 if (tempStats.seen_stat) {
8486                     tempStats.ok_to_send = 1;
8487                 }
8488
8489                 if (strchr(tempStats.movelist, '(') != NULL) {
8490                     tempStats.line_is_book = 1;
8491                     tempStats.nr_moves = 0;
8492                     tempStats.moves_left = 0;
8493                 } else {
8494                     tempStats.line_is_book = 0;
8495                 }
8496
8497                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8498                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8499
8500                 SendProgramStatsToFrontend( cps, &tempStats );
8501
8502                 /*
8503                     [AS] Protect the thinkOutput buffer from overflow... this
8504                     is only useful if buf1 hasn't overflowed first!
8505                 */
8506                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8507                          plylev,
8508                          (gameMode == TwoMachinesPlay ?
8509                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8510                          ((double) curscore) / 100.0,
8511                          prefixHint ? lastHint : "",
8512                          prefixHint ? " " : "" );
8513
8514                 if( buf1[0] != NULLCHAR ) {
8515                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8516
8517                     if( strlen(buf1) > max_len ) {
8518                         if( appData.debugMode) {
8519                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8520                         }
8521                         buf1[max_len+1] = '\0';
8522                     }
8523
8524                     strcat( thinkOutput, buf1 );
8525                 }
8526
8527                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8528                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8529                     DisplayMove(currentMove - 1);
8530                 }
8531                 return;
8532
8533             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8534                 /* crafty (9.25+) says "(only move) <move>"
8535                  * if there is only 1 legal move
8536                  */
8537                 sscanf(p, "(only move) %s", buf1);
8538                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8539                 sprintf(programStats.movelist, "%s (only move)", buf1);
8540                 programStats.depth = 1;
8541                 programStats.nr_moves = 1;
8542                 programStats.moves_left = 1;
8543                 programStats.nodes = 1;
8544                 programStats.time = 1;
8545                 programStats.got_only_move = 1;
8546
8547                 /* Not really, but we also use this member to
8548                    mean "line isn't going to change" (Crafty
8549                    isn't searching, so stats won't change) */
8550                 programStats.line_is_book = 1;
8551
8552                 SendProgramStatsToFrontend( cps, &programStats );
8553
8554                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8555                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8556                     DisplayMove(currentMove - 1);
8557                 }
8558                 return;
8559             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8560                               &time, &nodes, &plylev, &mvleft,
8561                               &mvtot, mvname) >= 5) {
8562                 /* The stat01: line is from Crafty (9.29+) in response
8563                    to the "." command */
8564                 programStats.seen_stat = 1;
8565                 cps->maybeThinking = TRUE;
8566
8567                 if (programStats.got_only_move || !appData.periodicUpdates)
8568                   return;
8569
8570                 programStats.depth = plylev;
8571                 programStats.time = time;
8572                 programStats.nodes = nodes;
8573                 programStats.moves_left = mvleft;
8574                 programStats.nr_moves = mvtot;
8575                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8576                 programStats.ok_to_send = 1;
8577                 programStats.movelist[0] = '\0';
8578
8579                 SendProgramStatsToFrontend( cps, &programStats );
8580
8581                 return;
8582
8583             } else if (strncmp(message,"++",2) == 0) {
8584                 /* Crafty 9.29+ outputs this */
8585                 programStats.got_fail = 2;
8586                 return;
8587
8588             } else if (strncmp(message,"--",2) == 0) {
8589                 /* Crafty 9.29+ outputs this */
8590                 programStats.got_fail = 1;
8591                 return;
8592
8593             } else if (thinkOutput[0] != NULLCHAR &&
8594                        strncmp(message, "    ", 4) == 0) {
8595                 unsigned message_len;
8596
8597                 p = message;
8598                 while (*p && *p == ' ') p++;
8599
8600                 message_len = strlen( p );
8601
8602                 /* [AS] Avoid buffer overflow */
8603                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8604                     strcat(thinkOutput, " ");
8605                     strcat(thinkOutput, p);
8606                 }
8607
8608                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8609                     strcat(programStats.movelist, " ");
8610                     strcat(programStats.movelist, p);
8611                 }
8612
8613                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8614                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8615                     DisplayMove(currentMove - 1);
8616                 }
8617                 return;
8618             }
8619         }
8620         else {
8621             buf1[0] = NULLCHAR;
8622
8623             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8624                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8625             {
8626                 ChessProgramStats cpstats;
8627
8628                 if (plyext != ' ' && plyext != '\t') {
8629                     time *= 100;
8630                 }
8631
8632                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8633                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8634                     curscore = -curscore;
8635                 }
8636
8637                 cpstats.depth = plylev;
8638                 cpstats.nodes = nodes;
8639                 cpstats.time = time;
8640                 cpstats.score = curscore;
8641                 cpstats.got_only_move = 0;
8642                 cpstats.movelist[0] = '\0';
8643
8644                 if (buf1[0] != NULLCHAR) {
8645                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8646                 }
8647
8648                 cpstats.ok_to_send = 0;
8649                 cpstats.line_is_book = 0;
8650                 cpstats.nr_moves = 0;
8651                 cpstats.moves_left = 0;
8652
8653                 SendProgramStatsToFrontend( cps, &cpstats );
8654             }
8655         }
8656     }
8657 }
8658
8659
8660 /* Parse a game score from the character string "game", and
8661    record it as the history of the current game.  The game
8662    score is NOT assumed to start from the standard position.
8663    The display is not updated in any way.
8664    */
8665 void
8666 ParseGameHistory(game)
8667      char *game;
8668 {
8669     ChessMove moveType;
8670     int fromX, fromY, toX, toY, boardIndex;
8671     char promoChar;
8672     char *p, *q;
8673     char buf[MSG_SIZ];
8674
8675     if (appData.debugMode)
8676       fprintf(debugFP, "Parsing game history: %s\n", game);
8677
8678     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8679     gameInfo.site = StrSave(appData.icsHost);
8680     gameInfo.date = PGNDate();
8681     gameInfo.round = StrSave("-");
8682
8683     /* Parse out names of players */
8684     while (*game == ' ') game++;
8685     p = buf;
8686     while (*game != ' ') *p++ = *game++;
8687     *p = NULLCHAR;
8688     gameInfo.white = StrSave(buf);
8689     while (*game == ' ') game++;
8690     p = buf;
8691     while (*game != ' ' && *game != '\n') *p++ = *game++;
8692     *p = NULLCHAR;
8693     gameInfo.black = StrSave(buf);
8694
8695     /* Parse moves */
8696     boardIndex = blackPlaysFirst ? 1 : 0;
8697     yynewstr(game);
8698     for (;;) {
8699         yyboardindex = boardIndex;
8700         moveType = (ChessMove) Myylex();
8701         switch (moveType) {
8702           case IllegalMove:             /* maybe suicide chess, etc. */
8703   if (appData.debugMode) {
8704     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8705     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8706     setbuf(debugFP, NULL);
8707   }
8708           case WhitePromotion:
8709           case BlackPromotion:
8710           case WhiteNonPromotion:
8711           case BlackNonPromotion:
8712           case NormalMove:
8713           case WhiteCapturesEnPassant:
8714           case BlackCapturesEnPassant:
8715           case WhiteKingSideCastle:
8716           case WhiteQueenSideCastle:
8717           case BlackKingSideCastle:
8718           case BlackQueenSideCastle:
8719           case WhiteKingSideCastleWild:
8720           case WhiteQueenSideCastleWild:
8721           case BlackKingSideCastleWild:
8722           case BlackQueenSideCastleWild:
8723           /* PUSH Fabien */
8724           case WhiteHSideCastleFR:
8725           case WhiteASideCastleFR:
8726           case BlackHSideCastleFR:
8727           case BlackASideCastleFR:
8728           /* POP Fabien */
8729             fromX = currentMoveString[0] - AAA;
8730             fromY = currentMoveString[1] - ONE;
8731             toX = currentMoveString[2] - AAA;
8732             toY = currentMoveString[3] - ONE;
8733             promoChar = currentMoveString[4];
8734             break;
8735           case WhiteDrop:
8736           case BlackDrop:
8737             fromX = moveType == WhiteDrop ?
8738               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8739             (int) CharToPiece(ToLower(currentMoveString[0]));
8740             fromY = DROP_RANK;
8741             toX = currentMoveString[2] - AAA;
8742             toY = currentMoveString[3] - ONE;
8743             promoChar = NULLCHAR;
8744             break;
8745           case AmbiguousMove:
8746             /* bug? */
8747             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8748   if (appData.debugMode) {
8749     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8750     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8751     setbuf(debugFP, NULL);
8752   }
8753             DisplayError(buf, 0);
8754             return;
8755           case ImpossibleMove:
8756             /* bug? */
8757             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8758   if (appData.debugMode) {
8759     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8760     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8761     setbuf(debugFP, NULL);
8762   }
8763             DisplayError(buf, 0);
8764             return;
8765           case EndOfFile:
8766             if (boardIndex < backwardMostMove) {
8767                 /* Oops, gap.  How did that happen? */
8768                 DisplayError(_("Gap in move list"), 0);
8769                 return;
8770             }
8771             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8772             if (boardIndex > forwardMostMove) {
8773                 forwardMostMove = boardIndex;
8774             }
8775             return;
8776           case ElapsedTime:
8777             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8778                 strcat(parseList[boardIndex-1], " ");
8779                 strcat(parseList[boardIndex-1], yy_text);
8780             }
8781             continue;
8782           case Comment:
8783           case PGNTag:
8784           case NAG:
8785           default:
8786             /* ignore */
8787             continue;
8788           case WhiteWins:
8789           case BlackWins:
8790           case GameIsDrawn:
8791           case GameUnfinished:
8792             if (gameMode == IcsExamining) {
8793                 if (boardIndex < backwardMostMove) {
8794                     /* Oops, gap.  How did that happen? */
8795                     return;
8796                 }
8797                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8798                 return;
8799             }
8800             gameInfo.result = moveType;
8801             p = strchr(yy_text, '{');
8802             if (p == NULL) p = strchr(yy_text, '(');
8803             if (p == NULL) {
8804                 p = yy_text;
8805                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8806             } else {
8807                 q = strchr(p, *p == '{' ? '}' : ')');
8808                 if (q != NULL) *q = NULLCHAR;
8809                 p++;
8810             }
8811             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8812             gameInfo.resultDetails = StrSave(p);
8813             continue;
8814         }
8815         if (boardIndex >= forwardMostMove &&
8816             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8817             backwardMostMove = blackPlaysFirst ? 1 : 0;
8818             return;
8819         }
8820         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8821                                  fromY, fromX, toY, toX, promoChar,
8822                                  parseList[boardIndex]);
8823         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8824         /* currentMoveString is set as a side-effect of yylex */
8825         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8826         strcat(moveList[boardIndex], "\n");
8827         boardIndex++;
8828         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8829         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8830           case MT_NONE:
8831           case MT_STALEMATE:
8832           default:
8833             break;
8834           case MT_CHECK:
8835             if(gameInfo.variant != VariantShogi)
8836                 strcat(parseList[boardIndex - 1], "+");
8837             break;
8838           case MT_CHECKMATE:
8839           case MT_STAINMATE:
8840             strcat(parseList[boardIndex - 1], "#");
8841             break;
8842         }
8843     }
8844 }
8845
8846
8847 /* Apply a move to the given board  */
8848 void
8849 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8850      int fromX, fromY, toX, toY;
8851      int promoChar;
8852      Board board;
8853 {
8854   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8855   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8856
8857     /* [HGM] compute & store e.p. status and castling rights for new position */
8858     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8859
8860       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8861       oldEP = (signed char)board[EP_STATUS];
8862       board[EP_STATUS] = EP_NONE;
8863
8864       if( board[toY][toX] != EmptySquare )
8865            board[EP_STATUS] = EP_CAPTURE;
8866
8867   if (fromY == DROP_RANK) {
8868         /* must be first */
8869         piece = board[toY][toX] = (ChessSquare) fromX;
8870   } else {
8871       int i;
8872
8873       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8874            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8875                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8876       } else
8877       if( board[fromY][fromX] == WhitePawn ) {
8878            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8879                board[EP_STATUS] = EP_PAWN_MOVE;
8880            if( toY-fromY==2) {
8881                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8882                         gameInfo.variant != VariantBerolina || toX < fromX)
8883                       board[EP_STATUS] = toX | berolina;
8884                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8885                         gameInfo.variant != VariantBerolina || toX > fromX)
8886                       board[EP_STATUS] = toX;
8887            }
8888       } else
8889       if( board[fromY][fromX] == BlackPawn ) {
8890            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8891                board[EP_STATUS] = EP_PAWN_MOVE;
8892            if( toY-fromY== -2) {
8893                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8894                         gameInfo.variant != VariantBerolina || toX < fromX)
8895                       board[EP_STATUS] = toX | berolina;
8896                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8897                         gameInfo.variant != VariantBerolina || toX > fromX)
8898                       board[EP_STATUS] = toX;
8899            }
8900        }
8901
8902        for(i=0; i<nrCastlingRights; i++) {
8903            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8904               board[CASTLING][i] == toX   && castlingRank[i] == toY
8905              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8906        }
8907
8908      if (fromX == toX && fromY == toY) return;
8909
8910      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8911      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8912      if(gameInfo.variant == VariantKnightmate)
8913          king += (int) WhiteUnicorn - (int) WhiteKing;
8914
8915     /* Code added by Tord: */
8916     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8917     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8918         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8919       board[fromY][fromX] = EmptySquare;
8920       board[toY][toX] = EmptySquare;
8921       if((toX > fromX) != (piece == WhiteRook)) {
8922         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8923       } else {
8924         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8925       }
8926     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8927                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8928       board[fromY][fromX] = EmptySquare;
8929       board[toY][toX] = EmptySquare;
8930       if((toX > fromX) != (piece == BlackRook)) {
8931         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8932       } else {
8933         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8934       }
8935     /* End of code added by Tord */
8936
8937     } else if (board[fromY][fromX] == king
8938         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8939         && toY == fromY && toX > fromX+1) {
8940         board[fromY][fromX] = EmptySquare;
8941         board[toY][toX] = king;
8942         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8943         board[fromY][BOARD_RGHT-1] = EmptySquare;
8944     } else if (board[fromY][fromX] == king
8945         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8946                && toY == fromY && toX < fromX-1) {
8947         board[fromY][fromX] = EmptySquare;
8948         board[toY][toX] = king;
8949         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8950         board[fromY][BOARD_LEFT] = EmptySquare;
8951     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8952                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8953                && toY >= BOARD_HEIGHT-promoRank
8954                ) {
8955         /* white pawn promotion */
8956         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8957         if (board[toY][toX] == EmptySquare) {
8958             board[toY][toX] = WhiteQueen;
8959         }
8960         if(gameInfo.variant==VariantBughouse ||
8961            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8962             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8963         board[fromY][fromX] = EmptySquare;
8964     } else if ((fromY == BOARD_HEIGHT-4)
8965                && (toX != fromX)
8966                && gameInfo.variant != VariantXiangqi
8967                && gameInfo.variant != VariantBerolina
8968                && (board[fromY][fromX] == WhitePawn)
8969                && (board[toY][toX] == EmptySquare)) {
8970         board[fromY][fromX] = EmptySquare;
8971         board[toY][toX] = WhitePawn;
8972         captured = board[toY - 1][toX];
8973         board[toY - 1][toX] = EmptySquare;
8974     } else if ((fromY == BOARD_HEIGHT-4)
8975                && (toX == fromX)
8976                && gameInfo.variant == VariantBerolina
8977                && (board[fromY][fromX] == WhitePawn)
8978                && (board[toY][toX] == EmptySquare)) {
8979         board[fromY][fromX] = EmptySquare;
8980         board[toY][toX] = WhitePawn;
8981         if(oldEP & EP_BEROLIN_A) {
8982                 captured = board[fromY][fromX-1];
8983                 board[fromY][fromX-1] = EmptySquare;
8984         }else{  captured = board[fromY][fromX+1];
8985                 board[fromY][fromX+1] = EmptySquare;
8986         }
8987     } else if (board[fromY][fromX] == king
8988         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8989                && toY == fromY && toX > fromX+1) {
8990         board[fromY][fromX] = EmptySquare;
8991         board[toY][toX] = king;
8992         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8993         board[fromY][BOARD_RGHT-1] = EmptySquare;
8994     } else if (board[fromY][fromX] == king
8995         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8996                && toY == fromY && toX < fromX-1) {
8997         board[fromY][fromX] = EmptySquare;
8998         board[toY][toX] = king;
8999         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9000         board[fromY][BOARD_LEFT] = EmptySquare;
9001     } else if (fromY == 7 && fromX == 3
9002                && board[fromY][fromX] == BlackKing
9003                && toY == 7 && toX == 5) {
9004         board[fromY][fromX] = EmptySquare;
9005         board[toY][toX] = BlackKing;
9006         board[fromY][7] = EmptySquare;
9007         board[toY][4] = BlackRook;
9008     } else if (fromY == 7 && fromX == 3
9009                && board[fromY][fromX] == BlackKing
9010                && toY == 7 && toX == 1) {
9011         board[fromY][fromX] = EmptySquare;
9012         board[toY][toX] = BlackKing;
9013         board[fromY][0] = EmptySquare;
9014         board[toY][2] = BlackRook;
9015     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9016                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9017                && toY < promoRank
9018                ) {
9019         /* black pawn promotion */
9020         board[toY][toX] = CharToPiece(ToLower(promoChar));
9021         if (board[toY][toX] == EmptySquare) {
9022             board[toY][toX] = BlackQueen;
9023         }
9024         if(gameInfo.variant==VariantBughouse ||
9025            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9026             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9027         board[fromY][fromX] = EmptySquare;
9028     } else if ((fromY == 3)
9029                && (toX != fromX)
9030                && gameInfo.variant != VariantXiangqi
9031                && gameInfo.variant != VariantBerolina
9032                && (board[fromY][fromX] == BlackPawn)
9033                && (board[toY][toX] == EmptySquare)) {
9034         board[fromY][fromX] = EmptySquare;
9035         board[toY][toX] = BlackPawn;
9036         captured = board[toY + 1][toX];
9037         board[toY + 1][toX] = EmptySquare;
9038     } else if ((fromY == 3)
9039                && (toX == fromX)
9040                && gameInfo.variant == VariantBerolina
9041                && (board[fromY][fromX] == BlackPawn)
9042                && (board[toY][toX] == EmptySquare)) {
9043         board[fromY][fromX] = EmptySquare;
9044         board[toY][toX] = BlackPawn;
9045         if(oldEP & EP_BEROLIN_A) {
9046                 captured = board[fromY][fromX-1];
9047                 board[fromY][fromX-1] = EmptySquare;
9048         }else{  captured = board[fromY][fromX+1];
9049                 board[fromY][fromX+1] = EmptySquare;
9050         }
9051     } else {
9052         board[toY][toX] = board[fromY][fromX];
9053         board[fromY][fromX] = EmptySquare;
9054     }
9055   }
9056
9057     if (gameInfo.holdingsWidth != 0) {
9058
9059       /* !!A lot more code needs to be written to support holdings  */
9060       /* [HGM] OK, so I have written it. Holdings are stored in the */
9061       /* penultimate board files, so they are automaticlly stored   */
9062       /* in the game history.                                       */
9063       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9064                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9065         /* Delete from holdings, by decreasing count */
9066         /* and erasing image if necessary            */
9067         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9068         if(p < (int) BlackPawn) { /* white drop */
9069              p -= (int)WhitePawn;
9070                  p = PieceToNumber((ChessSquare)p);
9071              if(p >= gameInfo.holdingsSize) p = 0;
9072              if(--board[p][BOARD_WIDTH-2] <= 0)
9073                   board[p][BOARD_WIDTH-1] = EmptySquare;
9074              if((int)board[p][BOARD_WIDTH-2] < 0)
9075                         board[p][BOARD_WIDTH-2] = 0;
9076         } else {                  /* black drop */
9077              p -= (int)BlackPawn;
9078                  p = PieceToNumber((ChessSquare)p);
9079              if(p >= gameInfo.holdingsSize) p = 0;
9080              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9081                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9082              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9083                         board[BOARD_HEIGHT-1-p][1] = 0;
9084         }
9085       }
9086       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9087           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9088         /* [HGM] holdings: Add to holdings, if holdings exist */
9089         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9090                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9091                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9092         }
9093         p = (int) captured;
9094         if (p >= (int) BlackPawn) {
9095           p -= (int)BlackPawn;
9096           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9097                   /* in Shogi restore piece to its original  first */
9098                   captured = (ChessSquare) (DEMOTED captured);
9099                   p = DEMOTED p;
9100           }
9101           p = PieceToNumber((ChessSquare)p);
9102           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9103           board[p][BOARD_WIDTH-2]++;
9104           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9105         } else {
9106           p -= (int)WhitePawn;
9107           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9108                   captured = (ChessSquare) (DEMOTED captured);
9109                   p = DEMOTED p;
9110           }
9111           p = PieceToNumber((ChessSquare)p);
9112           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9113           board[BOARD_HEIGHT-1-p][1]++;
9114           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9115         }
9116       }
9117     } else if (gameInfo.variant == VariantAtomic) {
9118       if (captured != EmptySquare) {
9119         int y, x;
9120         for (y = toY-1; y <= toY+1; y++) {
9121           for (x = toX-1; x <= toX+1; x++) {
9122             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9123                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9124               board[y][x] = EmptySquare;
9125             }
9126           }
9127         }
9128         board[toY][toX] = EmptySquare;
9129       }
9130     }
9131     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9132         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9133     } else
9134     if(promoChar == '+') {
9135         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9136         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9137     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9138         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9139     }
9140     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9141                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9142         // [HGM] superchess: take promotion piece out of holdings
9143         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9144         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9145             if(!--board[k][BOARD_WIDTH-2])
9146                 board[k][BOARD_WIDTH-1] = EmptySquare;
9147         } else {
9148             if(!--board[BOARD_HEIGHT-1-k][1])
9149                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9150         }
9151     }
9152
9153 }
9154
9155 /* Updates forwardMostMove */
9156 void
9157 MakeMove(fromX, fromY, toX, toY, promoChar)
9158      int fromX, fromY, toX, toY;
9159      int promoChar;
9160 {
9161 //    forwardMostMove++; // [HGM] bare: moved downstream
9162
9163     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9164         int timeLeft; static int lastLoadFlag=0; int king, piece;
9165         piece = boards[forwardMostMove][fromY][fromX];
9166         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9167         if(gameInfo.variant == VariantKnightmate)
9168             king += (int) WhiteUnicorn - (int) WhiteKing;
9169         if(forwardMostMove == 0) {
9170             if(blackPlaysFirst)
9171                 fprintf(serverMoves, "%s;", second.tidy);
9172             fprintf(serverMoves, "%s;", first.tidy);
9173             if(!blackPlaysFirst)
9174                 fprintf(serverMoves, "%s;", second.tidy);
9175         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9176         lastLoadFlag = loadFlag;
9177         // print base move
9178         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9179         // print castling suffix
9180         if( toY == fromY && piece == king ) {
9181             if(toX-fromX > 1)
9182                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9183             if(fromX-toX >1)
9184                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9185         }
9186         // e.p. suffix
9187         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9188              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9189              boards[forwardMostMove][toY][toX] == EmptySquare
9190              && fromX != toX && fromY != toY)
9191                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9192         // promotion suffix
9193         if(promoChar != NULLCHAR)
9194                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9195         if(!loadFlag) {
9196             fprintf(serverMoves, "/%d/%d",
9197                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9198             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9199             else                      timeLeft = blackTimeRemaining/1000;
9200             fprintf(serverMoves, "/%d", timeLeft);
9201         }
9202         fflush(serverMoves);
9203     }
9204
9205     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9206       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9207                         0, 1);
9208       return;
9209     }
9210     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9211     if (commentList[forwardMostMove+1] != NULL) {
9212         free(commentList[forwardMostMove+1]);
9213         commentList[forwardMostMove+1] = NULL;
9214     }
9215     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9216     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9217     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9218     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9219     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9220     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9221     gameInfo.result = GameUnfinished;
9222     if (gameInfo.resultDetails != NULL) {
9223         free(gameInfo.resultDetails);
9224         gameInfo.resultDetails = NULL;
9225     }
9226     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9227                               moveList[forwardMostMove - 1]);
9228     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9229                              PosFlags(forwardMostMove - 1),
9230                              fromY, fromX, toY, toX, promoChar,
9231                              parseList[forwardMostMove - 1]);
9232     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9233       case MT_NONE:
9234       case MT_STALEMATE:
9235       default:
9236         break;
9237       case MT_CHECK:
9238         if(gameInfo.variant != VariantShogi)
9239             strcat(parseList[forwardMostMove - 1], "+");
9240         break;
9241       case MT_CHECKMATE:
9242       case MT_STAINMATE:
9243         strcat(parseList[forwardMostMove - 1], "#");
9244         break;
9245     }
9246     if (appData.debugMode) {
9247         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9248     }
9249
9250 }
9251
9252 /* Updates currentMove if not pausing */
9253 void
9254 ShowMove(fromX, fromY, toX, toY)
9255 {
9256     int instant = (gameMode == PlayFromGameFile) ?
9257         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9258     if(appData.noGUI) return;
9259     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9260         if (!instant) {
9261             if (forwardMostMove == currentMove + 1) {
9262                 AnimateMove(boards[forwardMostMove - 1],
9263                             fromX, fromY, toX, toY);
9264             }
9265             if (appData.highlightLastMove) {
9266                 SetHighlights(fromX, fromY, toX, toY);
9267             }
9268         }
9269         currentMove = forwardMostMove;
9270     }
9271
9272     if (instant) return;
9273
9274     DisplayMove(currentMove - 1);
9275     DrawPosition(FALSE, boards[currentMove]);
9276     DisplayBothClocks();
9277     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9278     DisplayBook(currentMove);
9279 }
9280
9281 void SendEgtPath(ChessProgramState *cps)
9282 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9283         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9284
9285         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9286
9287         while(*p) {
9288             char c, *q = name+1, *r, *s;
9289
9290             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9291             while(*p && *p != ',') *q++ = *p++;
9292             *q++ = ':'; *q = 0;
9293             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9294                 strcmp(name, ",nalimov:") == 0 ) {
9295                 // take nalimov path from the menu-changeable option first, if it is defined
9296               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9297                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9298             } else
9299             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9300                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9301                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9302                 s = r = StrStr(s, ":") + 1; // beginning of path info
9303                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9304                 c = *r; *r = 0;             // temporarily null-terminate path info
9305                     *--q = 0;               // strip of trailig ':' from name
9306                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9307                 *r = c;
9308                 SendToProgram(buf,cps);     // send egtbpath command for this format
9309             }
9310             if(*p == ',') p++; // read away comma to position for next format name
9311         }
9312 }
9313
9314 void
9315 InitChessProgram(cps, setup)
9316      ChessProgramState *cps;
9317      int setup; /* [HGM] needed to setup FRC opening position */
9318 {
9319     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9320     if (appData.noChessProgram) return;
9321     hintRequested = FALSE;
9322     bookRequested = FALSE;
9323
9324     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9325     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9326     if(cps->memSize) { /* [HGM] memory */
9327       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9328         SendToProgram(buf, cps);
9329     }
9330     SendEgtPath(cps); /* [HGM] EGT */
9331     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9332       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9333         SendToProgram(buf, cps);
9334     }
9335
9336     SendToProgram(cps->initString, cps);
9337     if (gameInfo.variant != VariantNormal &&
9338         gameInfo.variant != VariantLoadable
9339         /* [HGM] also send variant if board size non-standard */
9340         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9341                                             ) {
9342       char *v = VariantName(gameInfo.variant);
9343       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9344         /* [HGM] in protocol 1 we have to assume all variants valid */
9345         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9346         DisplayFatalError(buf, 0, 1);
9347         return;
9348       }
9349
9350       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9351       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9352       if( gameInfo.variant == VariantXiangqi )
9353            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9354       if( gameInfo.variant == VariantShogi )
9355            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9356       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9357            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9358       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9359           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9360            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9361       if( gameInfo.variant == VariantCourier )
9362            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9363       if( gameInfo.variant == VariantSuper )
9364            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9365       if( gameInfo.variant == VariantGreat )
9366            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9367       if( gameInfo.variant == VariantSChess )
9368            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9369
9370       if(overruled) {
9371         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9372                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9373            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9374            if(StrStr(cps->variants, b) == NULL) {
9375                // specific sized variant not known, check if general sizing allowed
9376                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9377                    if(StrStr(cps->variants, "boardsize") == NULL) {
9378                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9379                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9380                        DisplayFatalError(buf, 0, 1);
9381                        return;
9382                    }
9383                    /* [HGM] here we really should compare with the maximum supported board size */
9384                }
9385            }
9386       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9387       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9388       SendToProgram(buf, cps);
9389     }
9390     currentlyInitializedVariant = gameInfo.variant;
9391
9392     /* [HGM] send opening position in FRC to first engine */
9393     if(setup) {
9394           SendToProgram("force\n", cps);
9395           SendBoard(cps, 0);
9396           /* engine is now in force mode! Set flag to wake it up after first move. */
9397           setboardSpoiledMachineBlack = 1;
9398     }
9399
9400     if (cps->sendICS) {
9401       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9402       SendToProgram(buf, cps);
9403     }
9404     cps->maybeThinking = FALSE;
9405     cps->offeredDraw = 0;
9406     if (!appData.icsActive) {
9407         SendTimeControl(cps, movesPerSession, timeControl,
9408                         timeIncrement, appData.searchDepth,
9409                         searchTime);
9410     }
9411     if (appData.showThinking
9412         // [HGM] thinking: four options require thinking output to be sent
9413         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9414                                 ) {
9415         SendToProgram("post\n", cps);
9416     }
9417     SendToProgram("hard\n", cps);
9418     if (!appData.ponderNextMove) {
9419         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9420            it without being sure what state we are in first.  "hard"
9421            is not a toggle, so that one is OK.
9422          */
9423         SendToProgram("easy\n", cps);
9424     }
9425     if (cps->usePing) {
9426       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9427       SendToProgram(buf, cps);
9428     }
9429     cps->initDone = TRUE;
9430 }
9431
9432
9433 void
9434 StartChessProgram(cps)
9435      ChessProgramState *cps;
9436 {
9437     char buf[MSG_SIZ];
9438     int err;
9439
9440     if (appData.noChessProgram) return;
9441     cps->initDone = FALSE;
9442
9443     if (strcmp(cps->host, "localhost") == 0) {
9444         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9445     } else if (*appData.remoteShell == NULLCHAR) {
9446         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9447     } else {
9448         if (*appData.remoteUser == NULLCHAR) {
9449           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9450                     cps->program);
9451         } else {
9452           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9453                     cps->host, appData.remoteUser, cps->program);
9454         }
9455         err = StartChildProcess(buf, "", &cps->pr);
9456     }
9457
9458     if (err != 0) {
9459       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9460         DisplayFatalError(buf, err, 1);
9461         cps->pr = NoProc;
9462         cps->isr = NULL;
9463         return;
9464     }
9465
9466     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9467     if (cps->protocolVersion > 1) {
9468       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9469       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9470       cps->comboCnt = 0;  //                and values of combo boxes
9471       SendToProgram(buf, cps);
9472     } else {
9473       SendToProgram("xboard\n", cps);
9474     }
9475 }
9476
9477 void
9478 TwoMachinesEventIfReady P((void))
9479 {
9480   static int curMess = 0;
9481   if (first.lastPing != first.lastPong) {
9482     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9483     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9484     return;
9485   }
9486   if (second.lastPing != second.lastPong) {
9487     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9488     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9489     return;
9490   }
9491   DisplayMessage("", ""); curMess = 0;
9492   ThawUI();
9493   TwoMachinesEvent();
9494 }
9495
9496 int
9497 CreateTourney(char *name)
9498 {
9499         FILE *f;
9500         if(name[0] == NULLCHAR) return 0;
9501         f = fopen(appData.tourneyFile, "r");
9502         if(f) { // file exists
9503             ParseArgsFromFile(f); // parse it
9504         } else {
9505             f = fopen(appData.tourneyFile, "w");
9506             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9507                 // create a file with tournament description
9508                 fprintf(f, "-participants {%s}\n", appData.participants);
9509                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9510                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9511                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9512                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9513                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9514                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9515                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9516                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9517                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9518                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9519                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9520                 if(searchTime > 0)
9521                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9522                 else {
9523                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9524                         fprintf(f, "-tc %s\n", appData.timeControl);
9525                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9526                 }
9527                 fprintf(f, "-results \"\"\n");
9528             }
9529         }
9530         fclose(f);
9531         appData.noChessProgram = FALSE;
9532         appData.clockMode = TRUE;
9533         SetGNUMode();
9534         return 1;
9535 }
9536
9537 #define MAXENGINES 1000
9538 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9539
9540 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9541 {
9542     char buf[MSG_SIZ], *p, *q;
9543     int i=1;
9544     while(*names) {
9545         p = names; q = buf;
9546         while(*p && *p != '\n') *q++ = *p++;
9547         *q = 0;
9548         if(engineList[i]) free(engineList[i]);
9549         engineList[i] = strdup(buf);
9550         if(*p == '\n') p++;
9551         TidyProgramName(engineList[i], "localhost", buf);
9552         if(engineMnemonic[i]) free(engineMnemonic[i]);
9553         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9554             strcat(buf, " (");
9555             sscanf(q + 8, "%s", buf + strlen(buf));
9556             strcat(buf, ")");
9557         }
9558         engineMnemonic[i] = strdup(buf);
9559         names = p; i++;
9560       if(i > MAXENGINES - 2) break;
9561     }
9562     engineList[i] = NULL;
9563 }
9564
9565 // following implemented as macro to avoid type limitations
9566 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9567
9568 void SwapEngines(int n)
9569 {   // swap settings for first engine and other engine (so far only some selected options)
9570     int h;
9571     char *p;
9572     if(n == 0) return;
9573     SWAP(directory, p)
9574     SWAP(chessProgram, p)
9575     SWAP(isUCI, h)
9576     SWAP(hasOwnBookUCI, h)
9577     SWAP(protocolVersion, h)
9578     SWAP(reuse, h)
9579     SWAP(scoreIsAbsolute, h)
9580     SWAP(timeOdds, h)
9581     SWAP(logo, p)
9582     SWAP(pgnName, p)
9583 }
9584
9585 void
9586 SetPlayer(int player)
9587 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9588     int i;
9589     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9590     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9591     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9592     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9593     if(mnemonic[i]) {
9594         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9595         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9596         ParseArgsFromString(buf);
9597     }
9598     free(engineName);
9599 }
9600
9601 int
9602 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9603 {   // determine players from game number
9604     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9605
9606     if(appData.tourneyType == 0) {
9607         roundsPerCycle = (nPlayers - 1) | 1;
9608         pairingsPerRound = nPlayers / 2;
9609     } else if(appData.tourneyType > 0) {
9610         roundsPerCycle = nPlayers - appData.tourneyType;
9611         pairingsPerRound = appData.tourneyType;
9612     }
9613     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9614     gamesPerCycle = gamesPerRound * roundsPerCycle;
9615     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9616     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9617     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9618     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9619     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9620     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9621
9622     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9623     if(appData.roundSync) *syncInterval = gamesPerRound;
9624
9625     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9626
9627     if(appData.tourneyType == 0) {
9628         if(curPairing == (nPlayers-1)/2 ) {
9629             *whitePlayer = curRound;
9630             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9631         } else {
9632             *whitePlayer = curRound - pairingsPerRound + curPairing;
9633             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9634             *blackPlayer = curRound + pairingsPerRound - curPairing;
9635             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9636         }
9637     } else if(appData.tourneyType > 0) {
9638         *whitePlayer = curPairing;
9639         *blackPlayer = curRound + appData.tourneyType;
9640     }
9641
9642     // take care of white/black alternation per round. 
9643     // For cycles and games this is already taken care of by default, derived from matchGame!
9644     return curRound & 1;
9645 }
9646
9647 int
9648 NextTourneyGame(int nr, int *swapColors)
9649 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9650     char *p, *q;
9651     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9652     FILE *tf;
9653     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9654     tf = fopen(appData.tourneyFile, "r");
9655     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9656     ParseArgsFromFile(tf); fclose(tf);
9657     InitTimeControls(); // TC might be altered from tourney file
9658
9659     p = appData.participants;
9660     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9661     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9662
9663     if(syncInterval) {
9664         p = q = appData.results;
9665         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9666         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9667             DisplayMessage(_("Waiting for other game(s)"),"");
9668             waitingForGame = TRUE;
9669             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9670             return 0;
9671         }
9672         waitingForGame = FALSE;
9673     }
9674
9675     if(first.pr != NoProc) return 1; // engines already loaded
9676
9677     // redefine engines, engine dir, etc.
9678     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9679     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9680     SwapEngines(1);
9681     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9682     SwapEngines(1);         // and make that valid for second engine by swapping
9683     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9684     InitEngine(&second, 1);
9685     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9686     return 1;
9687 }
9688
9689 void
9690 NextMatchGame()
9691 {   // performs game initialization that does not invoke engines, and then tries to start the game
9692     int firstWhite, swapColors = 0;
9693     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9694     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9695     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9696     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9697     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9698     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9699     Reset(FALSE, first.pr != NoProc);
9700     appData.noChessProgram = FALSE;
9701     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9702     TwoMachinesEvent();
9703 }
9704
9705 void UserAdjudicationEvent( int result )
9706 {
9707     ChessMove gameResult = GameIsDrawn;
9708
9709     if( result > 0 ) {
9710         gameResult = WhiteWins;
9711     }
9712     else if( result < 0 ) {
9713         gameResult = BlackWins;
9714     }
9715
9716     if( gameMode == TwoMachinesPlay ) {
9717         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9718     }
9719 }
9720
9721
9722 // [HGM] save: calculate checksum of game to make games easily identifiable
9723 int StringCheckSum(char *s)
9724 {
9725         int i = 0;
9726         if(s==NULL) return 0;
9727         while(*s) i = i*259 + *s++;
9728         return i;
9729 }
9730
9731 int GameCheckSum()
9732 {
9733         int i, sum=0;
9734         for(i=backwardMostMove; i<forwardMostMove; i++) {
9735                 sum += pvInfoList[i].depth;
9736                 sum += StringCheckSum(parseList[i]);
9737                 sum += StringCheckSum(commentList[i]);
9738                 sum *= 261;
9739         }
9740         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9741         return sum + StringCheckSum(commentList[i]);
9742 } // end of save patch
9743
9744 void
9745 GameEnds(result, resultDetails, whosays)
9746      ChessMove result;
9747      char *resultDetails;
9748      int whosays;
9749 {
9750     GameMode nextGameMode;
9751     int isIcsGame;
9752     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9753
9754     if(endingGame) return; /* [HGM] crash: forbid recursion */
9755     endingGame = 1;
9756     if(twoBoards) { // [HGM] dual: switch back to one board
9757         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9758         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9759     }
9760     if (appData.debugMode) {
9761       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9762               result, resultDetails ? resultDetails : "(null)", whosays);
9763     }
9764
9765     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9766
9767     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9768         /* If we are playing on ICS, the server decides when the
9769            game is over, but the engine can offer to draw, claim
9770            a draw, or resign.
9771          */
9772 #if ZIPPY
9773         if (appData.zippyPlay && first.initDone) {
9774             if (result == GameIsDrawn) {
9775                 /* In case draw still needs to be claimed */
9776                 SendToICS(ics_prefix);
9777                 SendToICS("draw\n");
9778             } else if (StrCaseStr(resultDetails, "resign")) {
9779                 SendToICS(ics_prefix);
9780                 SendToICS("resign\n");
9781             }
9782         }
9783 #endif
9784         endingGame = 0; /* [HGM] crash */
9785         return;
9786     }
9787
9788     /* If we're loading the game from a file, stop */
9789     if (whosays == GE_FILE) {
9790       (void) StopLoadGameTimer();
9791       gameFileFP = NULL;
9792     }
9793
9794     /* Cancel draw offers */
9795     first.offeredDraw = second.offeredDraw = 0;
9796
9797     /* If this is an ICS game, only ICS can really say it's done;
9798        if not, anyone can. */
9799     isIcsGame = (gameMode == IcsPlayingWhite ||
9800                  gameMode == IcsPlayingBlack ||
9801                  gameMode == IcsObserving    ||
9802                  gameMode == IcsExamining);
9803
9804     if (!isIcsGame || whosays == GE_ICS) {
9805         /* OK -- not an ICS game, or ICS said it was done */
9806         StopClocks();
9807         if (!isIcsGame && !appData.noChessProgram)
9808           SetUserThinkingEnables();
9809
9810         /* [HGM] if a machine claims the game end we verify this claim */
9811         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9812             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9813                 char claimer;
9814                 ChessMove trueResult = (ChessMove) -1;
9815
9816                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9817                                             first.twoMachinesColor[0] :
9818                                             second.twoMachinesColor[0] ;
9819
9820                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9821                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9822                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9823                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9824                 } else
9825                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9826                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9827                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9828                 } else
9829                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9830                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9831                 }
9832
9833                 // now verify win claims, but not in drop games, as we don't understand those yet
9834                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9835                                                  || gameInfo.variant == VariantGreat) &&
9836                     (result == WhiteWins && claimer == 'w' ||
9837                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9838                       if (appData.debugMode) {
9839                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9840                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9841                       }
9842                       if(result != trueResult) {
9843                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9844                               result = claimer == 'w' ? BlackWins : WhiteWins;
9845                               resultDetails = buf;
9846                       }
9847                 } else
9848                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9849                     && (forwardMostMove <= backwardMostMove ||
9850                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9851                         (claimer=='b')==(forwardMostMove&1))
9852                                                                                   ) {
9853                       /* [HGM] verify: draws that were not flagged are false claims */
9854                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9855                       result = claimer == 'w' ? BlackWins : WhiteWins;
9856                       resultDetails = buf;
9857                 }
9858                 /* (Claiming a loss is accepted no questions asked!) */
9859             }
9860             /* [HGM] bare: don't allow bare King to win */
9861             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9862                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9863                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9864                && result != GameIsDrawn)
9865             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9866                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9867                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9868                         if(p >= 0 && p <= (int)WhiteKing) k++;
9869                 }
9870                 if (appData.debugMode) {
9871                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9872                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9873                 }
9874                 if(k <= 1) {
9875                         result = GameIsDrawn;
9876                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9877                         resultDetails = buf;
9878                 }
9879             }
9880         }
9881
9882
9883         if(serverMoves != NULL && !loadFlag) { char c = '=';
9884             if(result==WhiteWins) c = '+';
9885             if(result==BlackWins) c = '-';
9886             if(resultDetails != NULL)
9887                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9888         }
9889         if (resultDetails != NULL) {
9890             gameInfo.result = result;
9891             gameInfo.resultDetails = StrSave(resultDetails);
9892
9893             /* display last move only if game was not loaded from file */
9894             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9895                 DisplayMove(currentMove - 1);
9896
9897             if (forwardMostMove != 0) {
9898                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9899                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9900                                                                 ) {
9901                     if (*appData.saveGameFile != NULLCHAR) {
9902                         SaveGameToFile(appData.saveGameFile, TRUE);
9903                     } else if (appData.autoSaveGames) {
9904                         AutoSaveGame();
9905                     }
9906                     if (*appData.savePositionFile != NULLCHAR) {
9907                         SavePositionToFile(appData.savePositionFile);
9908                     }
9909                 }
9910             }
9911
9912             /* Tell program how game ended in case it is learning */
9913             /* [HGM] Moved this to after saving the PGN, just in case */
9914             /* engine died and we got here through time loss. In that */
9915             /* case we will get a fatal error writing the pipe, which */
9916             /* would otherwise lose us the PGN.                       */
9917             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9918             /* output during GameEnds should never be fatal anymore   */
9919             if (gameMode == MachinePlaysWhite ||
9920                 gameMode == MachinePlaysBlack ||
9921                 gameMode == TwoMachinesPlay ||
9922                 gameMode == IcsPlayingWhite ||
9923                 gameMode == IcsPlayingBlack ||
9924                 gameMode == BeginningOfGame) {
9925                 char buf[MSG_SIZ];
9926                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9927                         resultDetails);
9928                 if (first.pr != NoProc) {
9929                     SendToProgram(buf, &first);
9930                 }
9931                 if (second.pr != NoProc &&
9932                     gameMode == TwoMachinesPlay) {
9933                     SendToProgram(buf, &second);
9934                 }
9935             }
9936         }
9937
9938         if (appData.icsActive) {
9939             if (appData.quietPlay &&
9940                 (gameMode == IcsPlayingWhite ||
9941                  gameMode == IcsPlayingBlack)) {
9942                 SendToICS(ics_prefix);
9943                 SendToICS("set shout 1\n");
9944             }
9945             nextGameMode = IcsIdle;
9946             ics_user_moved = FALSE;
9947             /* clean up premove.  It's ugly when the game has ended and the
9948              * premove highlights are still on the board.
9949              */
9950             if (gotPremove) {
9951               gotPremove = FALSE;
9952               ClearPremoveHighlights();
9953               DrawPosition(FALSE, boards[currentMove]);
9954             }
9955             if (whosays == GE_ICS) {
9956                 switch (result) {
9957                 case WhiteWins:
9958                     if (gameMode == IcsPlayingWhite)
9959                         PlayIcsWinSound();
9960                     else if(gameMode == IcsPlayingBlack)
9961                         PlayIcsLossSound();
9962                     break;
9963                 case BlackWins:
9964                     if (gameMode == IcsPlayingBlack)
9965                         PlayIcsWinSound();
9966                     else if(gameMode == IcsPlayingWhite)
9967                         PlayIcsLossSound();
9968                     break;
9969                 case GameIsDrawn:
9970                     PlayIcsDrawSound();
9971                     break;
9972                 default:
9973                     PlayIcsUnfinishedSound();
9974                 }
9975             }
9976         } else if (gameMode == EditGame ||
9977                    gameMode == PlayFromGameFile ||
9978                    gameMode == AnalyzeMode ||
9979                    gameMode == AnalyzeFile) {
9980             nextGameMode = gameMode;
9981         } else {
9982             nextGameMode = EndOfGame;
9983         }
9984         pausing = FALSE;
9985         ModeHighlight();
9986     } else {
9987         nextGameMode = gameMode;
9988     }
9989
9990     if (appData.noChessProgram) {
9991         gameMode = nextGameMode;
9992         ModeHighlight();
9993         endingGame = 0; /* [HGM] crash */
9994         return;
9995     }
9996
9997     if (first.reuse) {
9998         /* Put first chess program into idle state */
9999         if (first.pr != NoProc &&
10000             (gameMode == MachinePlaysWhite ||
10001              gameMode == MachinePlaysBlack ||
10002              gameMode == TwoMachinesPlay ||
10003              gameMode == IcsPlayingWhite ||
10004              gameMode == IcsPlayingBlack ||
10005              gameMode == BeginningOfGame)) {
10006             SendToProgram("force\n", &first);
10007             if (first.usePing) {
10008               char buf[MSG_SIZ];
10009               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10010               SendToProgram(buf, &first);
10011             }
10012         }
10013     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10014         /* Kill off first chess program */
10015         if (first.isr != NULL)
10016           RemoveInputSource(first.isr);
10017         first.isr = NULL;
10018
10019         if (first.pr != NoProc) {
10020             ExitAnalyzeMode();
10021             DoSleep( appData.delayBeforeQuit );
10022             SendToProgram("quit\n", &first);
10023             DoSleep( appData.delayAfterQuit );
10024             DestroyChildProcess(first.pr, first.useSigterm);
10025         }
10026         first.pr = NoProc;
10027     }
10028     if (second.reuse) {
10029         /* Put second chess program into idle state */
10030         if (second.pr != NoProc &&
10031             gameMode == TwoMachinesPlay) {
10032             SendToProgram("force\n", &second);
10033             if (second.usePing) {
10034               char buf[MSG_SIZ];
10035               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10036               SendToProgram(buf, &second);
10037             }
10038         }
10039     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10040         /* Kill off second chess program */
10041         if (second.isr != NULL)
10042           RemoveInputSource(second.isr);
10043         second.isr = NULL;
10044
10045         if (second.pr != NoProc) {
10046             DoSleep( appData.delayBeforeQuit );
10047             SendToProgram("quit\n", &second);
10048             DoSleep( appData.delayAfterQuit );
10049             DestroyChildProcess(second.pr, second.useSigterm);
10050         }
10051         second.pr = NoProc;
10052     }
10053
10054     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10055         char resChar = '=';
10056         switch (result) {
10057         case WhiteWins:
10058           resChar = '+';
10059           if (first.twoMachinesColor[0] == 'w') {
10060             first.matchWins++;
10061           } else {
10062             second.matchWins++;
10063           }
10064           break;
10065         case BlackWins:
10066           resChar = '-';
10067           if (first.twoMachinesColor[0] == 'b') {
10068             first.matchWins++;
10069           } else {
10070             second.matchWins++;
10071           }
10072           break;
10073         case GameUnfinished:
10074           resChar = ' ';
10075         default:
10076           break;
10077         }
10078
10079         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10080         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10081             ReserveGame(nextGame, resChar); // sets nextGame
10082             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10083         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10084
10085         if (nextGame <= appData.matchGames && !abortMatch) {
10086             gameMode = nextGameMode;
10087             matchGame = nextGame; // this will be overruled in tourney mode!
10088             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10089             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10090             endingGame = 0; /* [HGM] crash */
10091             return;
10092         } else {
10093             gameMode = nextGameMode;
10094             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10095                      first.tidy, second.tidy,
10096                      first.matchWins, second.matchWins,
10097                      appData.matchGames - (first.matchWins + second.matchWins));
10098             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10099             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10100                 first.twoMachinesColor = "black\n";
10101                 second.twoMachinesColor = "white\n";
10102             } else {
10103                 first.twoMachinesColor = "white\n";
10104                 second.twoMachinesColor = "black\n";
10105             }
10106         }
10107     }
10108     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10109         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10110       ExitAnalyzeMode();
10111     gameMode = nextGameMode;
10112     ModeHighlight();
10113     endingGame = 0;  /* [HGM] crash */
10114     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10115         if(matchMode == TRUE) { // match through command line: exit with or without popup
10116             if(ranking) {
10117                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10118                 else ExitEvent(0);
10119             } else DisplayFatalError(buf, 0, 0);
10120         } else { // match through menu; just stop, with or without popup
10121             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10122             if(ranking){
10123                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10124             } else DisplayNote(buf);
10125       }
10126       if(ranking) free(ranking);
10127     }
10128 }
10129
10130 /* Assumes program was just initialized (initString sent).
10131    Leaves program in force mode. */
10132 void
10133 FeedMovesToProgram(cps, upto)
10134      ChessProgramState *cps;
10135      int upto;
10136 {
10137     int i;
10138
10139     if (appData.debugMode)
10140       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10141               startedFromSetupPosition ? "position and " : "",
10142               backwardMostMove, upto, cps->which);
10143     if(currentlyInitializedVariant != gameInfo.variant) {
10144       char buf[MSG_SIZ];
10145         // [HGM] variantswitch: make engine aware of new variant
10146         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10147                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10148         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10149         SendToProgram(buf, cps);
10150         currentlyInitializedVariant = gameInfo.variant;
10151     }
10152     SendToProgram("force\n", cps);
10153     if (startedFromSetupPosition) {
10154         SendBoard(cps, backwardMostMove);
10155     if (appData.debugMode) {
10156         fprintf(debugFP, "feedMoves\n");
10157     }
10158     }
10159     for (i = backwardMostMove; i < upto; i++) {
10160         SendMoveToProgram(i, cps);
10161     }
10162 }
10163
10164
10165 int
10166 ResurrectChessProgram()
10167 {
10168      /* The chess program may have exited.
10169         If so, restart it and feed it all the moves made so far. */
10170     static int doInit = 0;
10171
10172     if (appData.noChessProgram) return 1;
10173
10174     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10175         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10176         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10177         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10178     } else {
10179         if (first.pr != NoProc) return 1;
10180         StartChessProgram(&first);
10181     }
10182     InitChessProgram(&first, FALSE);
10183     FeedMovesToProgram(&first, currentMove);
10184
10185     if (!first.sendTime) {
10186         /* can't tell gnuchess what its clock should read,
10187            so we bow to its notion. */
10188         ResetClocks();
10189         timeRemaining[0][currentMove] = whiteTimeRemaining;
10190         timeRemaining[1][currentMove] = blackTimeRemaining;
10191     }
10192
10193     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10194                 appData.icsEngineAnalyze) && first.analysisSupport) {
10195       SendToProgram("analyze\n", &first);
10196       first.analyzing = TRUE;
10197     }
10198     return 1;
10199 }
10200
10201 /*
10202  * Button procedures
10203  */
10204 void
10205 Reset(redraw, init)
10206      int redraw, init;
10207 {
10208     int i;
10209
10210     if (appData.debugMode) {
10211         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10212                 redraw, init, gameMode);
10213     }
10214     CleanupTail(); // [HGM] vari: delete any stored variations
10215     pausing = pauseExamInvalid = FALSE;
10216     startedFromSetupPosition = blackPlaysFirst = FALSE;
10217     firstMove = TRUE;
10218     whiteFlag = blackFlag = FALSE;
10219     userOfferedDraw = FALSE;
10220     hintRequested = bookRequested = FALSE;
10221     first.maybeThinking = FALSE;
10222     second.maybeThinking = FALSE;
10223     first.bookSuspend = FALSE; // [HGM] book
10224     second.bookSuspend = FALSE;
10225     thinkOutput[0] = NULLCHAR;
10226     lastHint[0] = NULLCHAR;
10227     ClearGameInfo(&gameInfo);
10228     gameInfo.variant = StringToVariant(appData.variant);
10229     ics_user_moved = ics_clock_paused = FALSE;
10230     ics_getting_history = H_FALSE;
10231     ics_gamenum = -1;
10232     white_holding[0] = black_holding[0] = NULLCHAR;
10233     ClearProgramStats();
10234     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10235
10236     ResetFrontEnd();
10237     ClearHighlights();
10238     flipView = appData.flipView;
10239     ClearPremoveHighlights();
10240     gotPremove = FALSE;
10241     alarmSounded = FALSE;
10242
10243     GameEnds(EndOfFile, NULL, GE_PLAYER);
10244     if(appData.serverMovesName != NULL) {
10245         /* [HGM] prepare to make moves file for broadcasting */
10246         clock_t t = clock();
10247         if(serverMoves != NULL) fclose(serverMoves);
10248         serverMoves = fopen(appData.serverMovesName, "r");
10249         if(serverMoves != NULL) {
10250             fclose(serverMoves);
10251             /* delay 15 sec before overwriting, so all clients can see end */
10252             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10253         }
10254         serverMoves = fopen(appData.serverMovesName, "w");
10255     }
10256
10257     ExitAnalyzeMode();
10258     gameMode = BeginningOfGame;
10259     ModeHighlight();
10260     if(appData.icsActive) gameInfo.variant = VariantNormal;
10261     currentMove = forwardMostMove = backwardMostMove = 0;
10262     InitPosition(redraw);
10263     for (i = 0; i < MAX_MOVES; i++) {
10264         if (commentList[i] != NULL) {
10265             free(commentList[i]);
10266             commentList[i] = NULL;
10267         }
10268     }
10269     ResetClocks();
10270     timeRemaining[0][0] = whiteTimeRemaining;
10271     timeRemaining[1][0] = blackTimeRemaining;
10272
10273     if (first.pr == NULL) {
10274         StartChessProgram(&first);
10275     }
10276     if (init) {
10277             InitChessProgram(&first, startedFromSetupPosition);
10278     }
10279     DisplayTitle("");
10280     DisplayMessage("", "");
10281     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10282     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10283 }
10284
10285 void
10286 AutoPlayGameLoop()
10287 {
10288     for (;;) {
10289         if (!AutoPlayOneMove())
10290           return;
10291         if (matchMode || appData.timeDelay == 0)
10292           continue;
10293         if (appData.timeDelay < 0)
10294           return;
10295         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10296         break;
10297     }
10298 }
10299
10300
10301 int
10302 AutoPlayOneMove()
10303 {
10304     int fromX, fromY, toX, toY;
10305
10306     if (appData.debugMode) {
10307       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10308     }
10309
10310     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10311       return FALSE;
10312
10313     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10314       pvInfoList[currentMove].depth = programStats.depth;
10315       pvInfoList[currentMove].score = programStats.score;
10316       pvInfoList[currentMove].time  = 0;
10317       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10318     }
10319
10320     if (currentMove >= forwardMostMove) {
10321       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10322       gameMode = EditGame;
10323       ModeHighlight();
10324
10325       /* [AS] Clear current move marker at the end of a game */
10326       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10327
10328       return FALSE;
10329     }
10330
10331     toX = moveList[currentMove][2] - AAA;
10332     toY = moveList[currentMove][3] - ONE;
10333
10334     if (moveList[currentMove][1] == '@') {
10335         if (appData.highlightLastMove) {
10336             SetHighlights(-1, -1, toX, toY);
10337         }
10338     } else {
10339         fromX = moveList[currentMove][0] - AAA;
10340         fromY = moveList[currentMove][1] - ONE;
10341
10342         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10343
10344         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10345
10346         if (appData.highlightLastMove) {
10347             SetHighlights(fromX, fromY, toX, toY);
10348         }
10349     }
10350     DisplayMove(currentMove);
10351     SendMoveToProgram(currentMove++, &first);
10352     DisplayBothClocks();
10353     DrawPosition(FALSE, boards[currentMove]);
10354     // [HGM] PV info: always display, routine tests if empty
10355     DisplayComment(currentMove - 1, commentList[currentMove]);
10356     return TRUE;
10357 }
10358
10359
10360 int
10361 LoadGameOneMove(readAhead)
10362      ChessMove readAhead;
10363 {
10364     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10365     char promoChar = NULLCHAR;
10366     ChessMove moveType;
10367     char move[MSG_SIZ];
10368     char *p, *q;
10369
10370     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10371         gameMode != AnalyzeMode && gameMode != Training) {
10372         gameFileFP = NULL;
10373         return FALSE;
10374     }
10375
10376     yyboardindex = forwardMostMove;
10377     if (readAhead != EndOfFile) {
10378       moveType = readAhead;
10379     } else {
10380       if (gameFileFP == NULL)
10381           return FALSE;
10382       moveType = (ChessMove) Myylex();
10383     }
10384
10385     done = FALSE;
10386     switch (moveType) {
10387       case Comment:
10388         if (appData.debugMode)
10389           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10390         p = yy_text;
10391
10392         /* append the comment but don't display it */
10393         AppendComment(currentMove, p, FALSE);
10394         return TRUE;
10395
10396       case WhiteCapturesEnPassant:
10397       case BlackCapturesEnPassant:
10398       case WhitePromotion:
10399       case BlackPromotion:
10400       case WhiteNonPromotion:
10401       case BlackNonPromotion:
10402       case NormalMove:
10403       case WhiteKingSideCastle:
10404       case WhiteQueenSideCastle:
10405       case BlackKingSideCastle:
10406       case BlackQueenSideCastle:
10407       case WhiteKingSideCastleWild:
10408       case WhiteQueenSideCastleWild:
10409       case BlackKingSideCastleWild:
10410       case BlackQueenSideCastleWild:
10411       /* PUSH Fabien */
10412       case WhiteHSideCastleFR:
10413       case WhiteASideCastleFR:
10414       case BlackHSideCastleFR:
10415       case BlackASideCastleFR:
10416       /* POP Fabien */
10417         if (appData.debugMode)
10418           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10419         fromX = currentMoveString[0] - AAA;
10420         fromY = currentMoveString[1] - ONE;
10421         toX = currentMoveString[2] - AAA;
10422         toY = currentMoveString[3] - ONE;
10423         promoChar = currentMoveString[4];
10424         break;
10425
10426       case WhiteDrop:
10427       case BlackDrop:
10428         if (appData.debugMode)
10429           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10430         fromX = moveType == WhiteDrop ?
10431           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10432         (int) CharToPiece(ToLower(currentMoveString[0]));
10433         fromY = DROP_RANK;
10434         toX = currentMoveString[2] - AAA;
10435         toY = currentMoveString[3] - ONE;
10436         break;
10437
10438       case WhiteWins:
10439       case BlackWins:
10440       case GameIsDrawn:
10441       case GameUnfinished:
10442         if (appData.debugMode)
10443           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10444         p = strchr(yy_text, '{');
10445         if (p == NULL) p = strchr(yy_text, '(');
10446         if (p == NULL) {
10447             p = yy_text;
10448             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10449         } else {
10450             q = strchr(p, *p == '{' ? '}' : ')');
10451             if (q != NULL) *q = NULLCHAR;
10452             p++;
10453         }
10454         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10455         GameEnds(moveType, p, GE_FILE);
10456         done = TRUE;
10457         if (cmailMsgLoaded) {
10458             ClearHighlights();
10459             flipView = WhiteOnMove(currentMove);
10460             if (moveType == GameUnfinished) flipView = !flipView;
10461             if (appData.debugMode)
10462               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10463         }
10464         break;
10465
10466       case EndOfFile:
10467         if (appData.debugMode)
10468           fprintf(debugFP, "Parser hit end of file\n");
10469         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10470           case MT_NONE:
10471           case MT_CHECK:
10472             break;
10473           case MT_CHECKMATE:
10474           case MT_STAINMATE:
10475             if (WhiteOnMove(currentMove)) {
10476                 GameEnds(BlackWins, "Black mates", GE_FILE);
10477             } else {
10478                 GameEnds(WhiteWins, "White mates", GE_FILE);
10479             }
10480             break;
10481           case MT_STALEMATE:
10482             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10483             break;
10484         }
10485         done = TRUE;
10486         break;
10487
10488       case MoveNumberOne:
10489         if (lastLoadGameStart == GNUChessGame) {
10490             /* GNUChessGames have numbers, but they aren't move numbers */
10491             if (appData.debugMode)
10492               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10493                       yy_text, (int) moveType);
10494             return LoadGameOneMove(EndOfFile); /* tail recursion */
10495         }
10496         /* else fall thru */
10497
10498       case XBoardGame:
10499       case GNUChessGame:
10500       case PGNTag:
10501         /* Reached start of next game in file */
10502         if (appData.debugMode)
10503           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10504         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10505           case MT_NONE:
10506           case MT_CHECK:
10507             break;
10508           case MT_CHECKMATE:
10509           case MT_STAINMATE:
10510             if (WhiteOnMove(currentMove)) {
10511                 GameEnds(BlackWins, "Black mates", GE_FILE);
10512             } else {
10513                 GameEnds(WhiteWins, "White mates", GE_FILE);
10514             }
10515             break;
10516           case MT_STALEMATE:
10517             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10518             break;
10519         }
10520         done = TRUE;
10521         break;
10522
10523       case PositionDiagram:     /* should not happen; ignore */
10524       case ElapsedTime:         /* ignore */
10525       case NAG:                 /* ignore */
10526         if (appData.debugMode)
10527           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10528                   yy_text, (int) moveType);
10529         return LoadGameOneMove(EndOfFile); /* tail recursion */
10530
10531       case IllegalMove:
10532         if (appData.testLegality) {
10533             if (appData.debugMode)
10534               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10535             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10536                     (forwardMostMove / 2) + 1,
10537                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10538             DisplayError(move, 0);
10539             done = TRUE;
10540         } else {
10541             if (appData.debugMode)
10542               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10543                       yy_text, currentMoveString);
10544             fromX = currentMoveString[0] - AAA;
10545             fromY = currentMoveString[1] - ONE;
10546             toX = currentMoveString[2] - AAA;
10547             toY = currentMoveString[3] - ONE;
10548             promoChar = currentMoveString[4];
10549         }
10550         break;
10551
10552       case AmbiguousMove:
10553         if (appData.debugMode)
10554           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10555         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10556                 (forwardMostMove / 2) + 1,
10557                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10558         DisplayError(move, 0);
10559         done = TRUE;
10560         break;
10561
10562       default:
10563       case ImpossibleMove:
10564         if (appData.debugMode)
10565           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10566         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10567                 (forwardMostMove / 2) + 1,
10568                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10569         DisplayError(move, 0);
10570         done = TRUE;
10571         break;
10572     }
10573
10574     if (done) {
10575         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10576             DrawPosition(FALSE, boards[currentMove]);
10577             DisplayBothClocks();
10578             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10579               DisplayComment(currentMove - 1, commentList[currentMove]);
10580         }
10581         (void) StopLoadGameTimer();
10582         gameFileFP = NULL;
10583         cmailOldMove = forwardMostMove;
10584         return FALSE;
10585     } else {
10586         /* currentMoveString is set as a side-effect of yylex */
10587
10588         thinkOutput[0] = NULLCHAR;
10589         MakeMove(fromX, fromY, toX, toY, promoChar);
10590         currentMove = forwardMostMove;
10591         return TRUE;
10592     }
10593 }
10594
10595 /* Load the nth game from the given file */
10596 int
10597 LoadGameFromFile(filename, n, title, useList)
10598      char *filename;
10599      int n;
10600      char *title;
10601      /*Boolean*/ int useList;
10602 {
10603     FILE *f;
10604     char buf[MSG_SIZ];
10605
10606     if (strcmp(filename, "-") == 0) {
10607         f = stdin;
10608         title = "stdin";
10609     } else {
10610         f = fopen(filename, "rb");
10611         if (f == NULL) {
10612           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10613             DisplayError(buf, errno);
10614             return FALSE;
10615         }
10616     }
10617     if (fseek(f, 0, 0) == -1) {
10618         /* f is not seekable; probably a pipe */
10619         useList = FALSE;
10620     }
10621     if (useList && n == 0) {
10622         int error = GameListBuild(f);
10623         if (error) {
10624             DisplayError(_("Cannot build game list"), error);
10625         } else if (!ListEmpty(&gameList) &&
10626                    ((ListGame *) gameList.tailPred)->number > 1) {
10627             GameListPopUp(f, title);
10628             return TRUE;
10629         }
10630         GameListDestroy();
10631         n = 1;
10632     }
10633     if (n == 0) n = 1;
10634     return LoadGame(f, n, title, FALSE);
10635 }
10636
10637
10638 void
10639 MakeRegisteredMove()
10640 {
10641     int fromX, fromY, toX, toY;
10642     char promoChar;
10643     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10644         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10645           case CMAIL_MOVE:
10646           case CMAIL_DRAW:
10647             if (appData.debugMode)
10648               fprintf(debugFP, "Restoring %s for game %d\n",
10649                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10650
10651             thinkOutput[0] = NULLCHAR;
10652             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10653             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10654             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10655             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10656             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10657             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10658             MakeMove(fromX, fromY, toX, toY, promoChar);
10659             ShowMove(fromX, fromY, toX, toY);
10660
10661             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10662               case MT_NONE:
10663               case MT_CHECK:
10664                 break;
10665
10666               case MT_CHECKMATE:
10667               case MT_STAINMATE:
10668                 if (WhiteOnMove(currentMove)) {
10669                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10670                 } else {
10671                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10672                 }
10673                 break;
10674
10675               case MT_STALEMATE:
10676                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10677                 break;
10678             }
10679
10680             break;
10681
10682           case CMAIL_RESIGN:
10683             if (WhiteOnMove(currentMove)) {
10684                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10685             } else {
10686                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10687             }
10688             break;
10689
10690           case CMAIL_ACCEPT:
10691             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10692             break;
10693
10694           default:
10695             break;
10696         }
10697     }
10698
10699     return;
10700 }
10701
10702 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10703 int
10704 CmailLoadGame(f, gameNumber, title, useList)
10705      FILE *f;
10706      int gameNumber;
10707      char *title;
10708      int useList;
10709 {
10710     int retVal;
10711
10712     if (gameNumber > nCmailGames) {
10713         DisplayError(_("No more games in this message"), 0);
10714         return FALSE;
10715     }
10716     if (f == lastLoadGameFP) {
10717         int offset = gameNumber - lastLoadGameNumber;
10718         if (offset == 0) {
10719             cmailMsg[0] = NULLCHAR;
10720             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10721                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10722                 nCmailMovesRegistered--;
10723             }
10724             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10725             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10726                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10727             }
10728         } else {
10729             if (! RegisterMove()) return FALSE;
10730         }
10731     }
10732
10733     retVal = LoadGame(f, gameNumber, title, useList);
10734
10735     /* Make move registered during previous look at this game, if any */
10736     MakeRegisteredMove();
10737
10738     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10739         commentList[currentMove]
10740           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10741         DisplayComment(currentMove - 1, commentList[currentMove]);
10742     }
10743
10744     return retVal;
10745 }
10746
10747 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10748 int
10749 ReloadGame(offset)
10750      int offset;
10751 {
10752     int gameNumber = lastLoadGameNumber + offset;
10753     if (lastLoadGameFP == NULL) {
10754         DisplayError(_("No game has been loaded yet"), 0);
10755         return FALSE;
10756     }
10757     if (gameNumber <= 0) {
10758         DisplayError(_("Can't back up any further"), 0);
10759         return FALSE;
10760     }
10761     if (cmailMsgLoaded) {
10762         return CmailLoadGame(lastLoadGameFP, gameNumber,
10763                              lastLoadGameTitle, lastLoadGameUseList);
10764     } else {
10765         return LoadGame(lastLoadGameFP, gameNumber,
10766                         lastLoadGameTitle, lastLoadGameUseList);
10767     }
10768 }
10769
10770
10771
10772 /* Load the nth game from open file f */
10773 int
10774 LoadGame(f, gameNumber, title, useList)
10775      FILE *f;
10776      int gameNumber;
10777      char *title;
10778      int useList;
10779 {
10780     ChessMove cm;
10781     char buf[MSG_SIZ];
10782     int gn = gameNumber;
10783     ListGame *lg = NULL;
10784     int numPGNTags = 0;
10785     int err;
10786     GameMode oldGameMode;
10787     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10788
10789     if (appData.debugMode)
10790         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10791
10792     if (gameMode == Training )
10793         SetTrainingModeOff();
10794
10795     oldGameMode = gameMode;
10796     if (gameMode != BeginningOfGame) {
10797       Reset(FALSE, TRUE);
10798     }
10799
10800     gameFileFP = f;
10801     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10802         fclose(lastLoadGameFP);
10803     }
10804
10805     if (useList) {
10806         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10807
10808         if (lg) {
10809             fseek(f, lg->offset, 0);
10810             GameListHighlight(gameNumber);
10811             gn = 1;
10812         }
10813         else {
10814             DisplayError(_("Game number out of range"), 0);
10815             return FALSE;
10816         }
10817     } else {
10818         GameListDestroy();
10819         if (fseek(f, 0, 0) == -1) {
10820             if (f == lastLoadGameFP ?
10821                 gameNumber == lastLoadGameNumber + 1 :
10822                 gameNumber == 1) {
10823                 gn = 1;
10824             } else {
10825                 DisplayError(_("Can't seek on game file"), 0);
10826                 return FALSE;
10827             }
10828         }
10829     }
10830     lastLoadGameFP = f;
10831     lastLoadGameNumber = gameNumber;
10832     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10833     lastLoadGameUseList = useList;
10834
10835     yynewfile(f);
10836
10837     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10838       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10839                 lg->gameInfo.black);
10840             DisplayTitle(buf);
10841     } else if (*title != NULLCHAR) {
10842         if (gameNumber > 1) {
10843           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10844             DisplayTitle(buf);
10845         } else {
10846             DisplayTitle(title);
10847         }
10848     }
10849
10850     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10851         gameMode = PlayFromGameFile;
10852         ModeHighlight();
10853     }
10854
10855     currentMove = forwardMostMove = backwardMostMove = 0;
10856     CopyBoard(boards[0], initialPosition);
10857     StopClocks();
10858
10859     /*
10860      * Skip the first gn-1 games in the file.
10861      * Also skip over anything that precedes an identifiable
10862      * start of game marker, to avoid being confused by
10863      * garbage at the start of the file.  Currently
10864      * recognized start of game markers are the move number "1",
10865      * the pattern "gnuchess .* game", the pattern
10866      * "^[#;%] [^ ]* game file", and a PGN tag block.
10867      * A game that starts with one of the latter two patterns
10868      * will also have a move number 1, possibly
10869      * following a position diagram.
10870      * 5-4-02: Let's try being more lenient and allowing a game to
10871      * start with an unnumbered move.  Does that break anything?
10872      */
10873     cm = lastLoadGameStart = EndOfFile;
10874     while (gn > 0) {
10875         yyboardindex = forwardMostMove;
10876         cm = (ChessMove) Myylex();
10877         switch (cm) {
10878           case EndOfFile:
10879             if (cmailMsgLoaded) {
10880                 nCmailGames = CMAIL_MAX_GAMES - gn;
10881             } else {
10882                 Reset(TRUE, TRUE);
10883                 DisplayError(_("Game not found in file"), 0);
10884             }
10885             return FALSE;
10886
10887           case GNUChessGame:
10888           case XBoardGame:
10889             gn--;
10890             lastLoadGameStart = cm;
10891             break;
10892
10893           case MoveNumberOne:
10894             switch (lastLoadGameStart) {
10895               case GNUChessGame:
10896               case XBoardGame:
10897               case PGNTag:
10898                 break;
10899               case MoveNumberOne:
10900               case EndOfFile:
10901                 gn--;           /* count this game */
10902                 lastLoadGameStart = cm;
10903                 break;
10904               default:
10905                 /* impossible */
10906                 break;
10907             }
10908             break;
10909
10910           case PGNTag:
10911             switch (lastLoadGameStart) {
10912               case GNUChessGame:
10913               case PGNTag:
10914               case MoveNumberOne:
10915               case EndOfFile:
10916                 gn--;           /* count this game */
10917                 lastLoadGameStart = cm;
10918                 break;
10919               case XBoardGame:
10920                 lastLoadGameStart = cm; /* game counted already */
10921                 break;
10922               default:
10923                 /* impossible */
10924                 break;
10925             }
10926             if (gn > 0) {
10927                 do {
10928                     yyboardindex = forwardMostMove;
10929                     cm = (ChessMove) Myylex();
10930                 } while (cm == PGNTag || cm == Comment);
10931             }
10932             break;
10933
10934           case WhiteWins:
10935           case BlackWins:
10936           case GameIsDrawn:
10937             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10938                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10939                     != CMAIL_OLD_RESULT) {
10940                     nCmailResults ++ ;
10941                     cmailResult[  CMAIL_MAX_GAMES
10942                                 - gn - 1] = CMAIL_OLD_RESULT;
10943                 }
10944             }
10945             break;
10946
10947           case NormalMove:
10948             /* Only a NormalMove can be at the start of a game
10949              * without a position diagram. */
10950             if (lastLoadGameStart == EndOfFile ) {
10951               gn--;
10952               lastLoadGameStart = MoveNumberOne;
10953             }
10954             break;
10955
10956           default:
10957             break;
10958         }
10959     }
10960
10961     if (appData.debugMode)
10962       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10963
10964     if (cm == XBoardGame) {
10965         /* Skip any header junk before position diagram and/or move 1 */
10966         for (;;) {
10967             yyboardindex = forwardMostMove;
10968             cm = (ChessMove) Myylex();
10969
10970             if (cm == EndOfFile ||
10971                 cm == GNUChessGame || cm == XBoardGame) {
10972                 /* Empty game; pretend end-of-file and handle later */
10973                 cm = EndOfFile;
10974                 break;
10975             }
10976
10977             if (cm == MoveNumberOne || cm == PositionDiagram ||
10978                 cm == PGNTag || cm == Comment)
10979               break;
10980         }
10981     } else if (cm == GNUChessGame) {
10982         if (gameInfo.event != NULL) {
10983             free(gameInfo.event);
10984         }
10985         gameInfo.event = StrSave(yy_text);
10986     }
10987
10988     startedFromSetupPosition = FALSE;
10989     while (cm == PGNTag) {
10990         if (appData.debugMode)
10991           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10992         err = ParsePGNTag(yy_text, &gameInfo);
10993         if (!err) numPGNTags++;
10994
10995         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10996         if(gameInfo.variant != oldVariant) {
10997             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10998             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10999             InitPosition(TRUE);
11000             oldVariant = gameInfo.variant;
11001             if (appData.debugMode)
11002               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11003         }
11004
11005
11006         if (gameInfo.fen != NULL) {
11007           Board initial_position;
11008           startedFromSetupPosition = TRUE;
11009           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11010             Reset(TRUE, TRUE);
11011             DisplayError(_("Bad FEN position in file"), 0);
11012             return FALSE;
11013           }
11014           CopyBoard(boards[0], initial_position);
11015           if (blackPlaysFirst) {
11016             currentMove = forwardMostMove = backwardMostMove = 1;
11017             CopyBoard(boards[1], initial_position);
11018             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11019             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11020             timeRemaining[0][1] = whiteTimeRemaining;
11021             timeRemaining[1][1] = blackTimeRemaining;
11022             if (commentList[0] != NULL) {
11023               commentList[1] = commentList[0];
11024               commentList[0] = NULL;
11025             }
11026           } else {
11027             currentMove = forwardMostMove = backwardMostMove = 0;
11028           }
11029           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11030           {   int i;
11031               initialRulePlies = FENrulePlies;
11032               for( i=0; i< nrCastlingRights; i++ )
11033                   initialRights[i] = initial_position[CASTLING][i];
11034           }
11035           yyboardindex = forwardMostMove;
11036           free(gameInfo.fen);
11037           gameInfo.fen = NULL;
11038         }
11039
11040         yyboardindex = forwardMostMove;
11041         cm = (ChessMove) Myylex();
11042
11043         /* Handle comments interspersed among the tags */
11044         while (cm == Comment) {
11045             char *p;
11046             if (appData.debugMode)
11047               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11048             p = yy_text;
11049             AppendComment(currentMove, p, FALSE);
11050             yyboardindex = forwardMostMove;
11051             cm = (ChessMove) Myylex();
11052         }
11053     }
11054
11055     /* don't rely on existence of Event tag since if game was
11056      * pasted from clipboard the Event tag may not exist
11057      */
11058     if (numPGNTags > 0){
11059         char *tags;
11060         if (gameInfo.variant == VariantNormal) {
11061           VariantClass v = StringToVariant(gameInfo.event);
11062           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11063           if(v < VariantShogi) gameInfo.variant = v;
11064         }
11065         if (!matchMode) {
11066           if( appData.autoDisplayTags ) {
11067             tags = PGNTags(&gameInfo);
11068             TagsPopUp(tags, CmailMsg());
11069             free(tags);
11070           }
11071         }
11072     } else {
11073         /* Make something up, but don't display it now */
11074         SetGameInfo();
11075         TagsPopDown();
11076     }
11077
11078     if (cm == PositionDiagram) {
11079         int i, j;
11080         char *p;
11081         Board initial_position;
11082
11083         if (appData.debugMode)
11084           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11085
11086         if (!startedFromSetupPosition) {
11087             p = yy_text;
11088             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11089               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11090                 switch (*p) {
11091                   case '{':
11092                   case '[':
11093                   case '-':
11094                   case ' ':
11095                   case '\t':
11096                   case '\n':
11097                   case '\r':
11098                     break;
11099                   default:
11100                     initial_position[i][j++] = CharToPiece(*p);
11101                     break;
11102                 }
11103             while (*p == ' ' || *p == '\t' ||
11104                    *p == '\n' || *p == '\r') p++;
11105
11106             if (strncmp(p, "black", strlen("black"))==0)
11107               blackPlaysFirst = TRUE;
11108             else
11109               blackPlaysFirst = FALSE;
11110             startedFromSetupPosition = TRUE;
11111
11112             CopyBoard(boards[0], initial_position);
11113             if (blackPlaysFirst) {
11114                 currentMove = forwardMostMove = backwardMostMove = 1;
11115                 CopyBoard(boards[1], initial_position);
11116                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11117                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11118                 timeRemaining[0][1] = whiteTimeRemaining;
11119                 timeRemaining[1][1] = blackTimeRemaining;
11120                 if (commentList[0] != NULL) {
11121                     commentList[1] = commentList[0];
11122                     commentList[0] = NULL;
11123                 }
11124             } else {
11125                 currentMove = forwardMostMove = backwardMostMove = 0;
11126             }
11127         }
11128         yyboardindex = forwardMostMove;
11129         cm = (ChessMove) Myylex();
11130     }
11131
11132     if (first.pr == NoProc) {
11133         StartChessProgram(&first);
11134     }
11135     InitChessProgram(&first, FALSE);
11136     SendToProgram("force\n", &first);
11137     if (startedFromSetupPosition) {
11138         SendBoard(&first, forwardMostMove);
11139     if (appData.debugMode) {
11140         fprintf(debugFP, "Load Game\n");
11141     }
11142         DisplayBothClocks();
11143     }
11144
11145     /* [HGM] server: flag to write setup moves in broadcast file as one */
11146     loadFlag = appData.suppressLoadMoves;
11147
11148     while (cm == Comment) {
11149         char *p;
11150         if (appData.debugMode)
11151           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11152         p = yy_text;
11153         AppendComment(currentMove, p, FALSE);
11154         yyboardindex = forwardMostMove;
11155         cm = (ChessMove) Myylex();
11156     }
11157
11158     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11159         cm == WhiteWins || cm == BlackWins ||
11160         cm == GameIsDrawn || cm == GameUnfinished) {
11161         DisplayMessage("", _("No moves in game"));
11162         if (cmailMsgLoaded) {
11163             if (appData.debugMode)
11164               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11165             ClearHighlights();
11166             flipView = FALSE;
11167         }
11168         DrawPosition(FALSE, boards[currentMove]);
11169         DisplayBothClocks();
11170         gameMode = EditGame;
11171         ModeHighlight();
11172         gameFileFP = NULL;
11173         cmailOldMove = 0;
11174         return TRUE;
11175     }
11176
11177     // [HGM] PV info: routine tests if comment empty
11178     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11179         DisplayComment(currentMove - 1, commentList[currentMove]);
11180     }
11181     if (!matchMode && appData.timeDelay != 0)
11182       DrawPosition(FALSE, boards[currentMove]);
11183
11184     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11185       programStats.ok_to_send = 1;
11186     }
11187
11188     /* if the first token after the PGN tags is a move
11189      * and not move number 1, retrieve it from the parser
11190      */
11191     if (cm != MoveNumberOne)
11192         LoadGameOneMove(cm);
11193
11194     /* load the remaining moves from the file */
11195     while (LoadGameOneMove(EndOfFile)) {
11196       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11197       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11198     }
11199
11200     /* rewind to the start of the game */
11201     currentMove = backwardMostMove;
11202
11203     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11204
11205     if (oldGameMode == AnalyzeFile ||
11206         oldGameMode == AnalyzeMode) {
11207       AnalyzeFileEvent();
11208     }
11209
11210     if (matchMode || appData.timeDelay == 0) {
11211       ToEndEvent();
11212       gameMode = EditGame;
11213       ModeHighlight();
11214     } else if (appData.timeDelay > 0) {
11215       AutoPlayGameLoop();
11216     }
11217
11218     if (appData.debugMode)
11219         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11220
11221     loadFlag = 0; /* [HGM] true game starts */
11222     return TRUE;
11223 }
11224
11225 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11226 int
11227 ReloadPosition(offset)
11228      int offset;
11229 {
11230     int positionNumber = lastLoadPositionNumber + offset;
11231     if (lastLoadPositionFP == NULL) {
11232         DisplayError(_("No position has been loaded yet"), 0);
11233         return FALSE;
11234     }
11235     if (positionNumber <= 0) {
11236         DisplayError(_("Can't back up any further"), 0);
11237         return FALSE;
11238     }
11239     return LoadPosition(lastLoadPositionFP, positionNumber,
11240                         lastLoadPositionTitle);
11241 }
11242
11243 /* Load the nth position from the given file */
11244 int
11245 LoadPositionFromFile(filename, n, title)
11246      char *filename;
11247      int n;
11248      char *title;
11249 {
11250     FILE *f;
11251     char buf[MSG_SIZ];
11252
11253     if (strcmp(filename, "-") == 0) {
11254         return LoadPosition(stdin, n, "stdin");
11255     } else {
11256         f = fopen(filename, "rb");
11257         if (f == NULL) {
11258             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11259             DisplayError(buf, errno);
11260             return FALSE;
11261         } else {
11262             return LoadPosition(f, n, title);
11263         }
11264     }
11265 }
11266
11267 /* Load the nth position from the given open file, and close it */
11268 int
11269 LoadPosition(f, positionNumber, title)
11270      FILE *f;
11271      int positionNumber;
11272      char *title;
11273 {
11274     char *p, line[MSG_SIZ];
11275     Board initial_position;
11276     int i, j, fenMode, pn;
11277
11278     if (gameMode == Training )
11279         SetTrainingModeOff();
11280
11281     if (gameMode != BeginningOfGame) {
11282         Reset(FALSE, TRUE);
11283     }
11284     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11285         fclose(lastLoadPositionFP);
11286     }
11287     if (positionNumber == 0) positionNumber = 1;
11288     lastLoadPositionFP = f;
11289     lastLoadPositionNumber = positionNumber;
11290     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11291     if (first.pr == NoProc) {
11292       StartChessProgram(&first);
11293       InitChessProgram(&first, FALSE);
11294     }
11295     pn = positionNumber;
11296     if (positionNumber < 0) {
11297         /* Negative position number means to seek to that byte offset */
11298         if (fseek(f, -positionNumber, 0) == -1) {
11299             DisplayError(_("Can't seek on position file"), 0);
11300             return FALSE;
11301         };
11302         pn = 1;
11303     } else {
11304         if (fseek(f, 0, 0) == -1) {
11305             if (f == lastLoadPositionFP ?
11306                 positionNumber == lastLoadPositionNumber + 1 :
11307                 positionNumber == 1) {
11308                 pn = 1;
11309             } else {
11310                 DisplayError(_("Can't seek on position file"), 0);
11311                 return FALSE;
11312             }
11313         }
11314     }
11315     /* See if this file is FEN or old-style xboard */
11316     if (fgets(line, MSG_SIZ, f) == NULL) {
11317         DisplayError(_("Position not found in file"), 0);
11318         return FALSE;
11319     }
11320     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11321     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11322
11323     if (pn >= 2) {
11324         if (fenMode || line[0] == '#') pn--;
11325         while (pn > 0) {
11326             /* skip positions before number pn */
11327             if (fgets(line, MSG_SIZ, f) == NULL) {
11328                 Reset(TRUE, TRUE);
11329                 DisplayError(_("Position not found in file"), 0);
11330                 return FALSE;
11331             }
11332             if (fenMode || line[0] == '#') pn--;
11333         }
11334     }
11335
11336     if (fenMode) {
11337         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11338             DisplayError(_("Bad FEN position in file"), 0);
11339             return FALSE;
11340         }
11341     } else {
11342         (void) fgets(line, MSG_SIZ, f);
11343         (void) fgets(line, MSG_SIZ, f);
11344
11345         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11346             (void) fgets(line, MSG_SIZ, f);
11347             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11348                 if (*p == ' ')
11349                   continue;
11350                 initial_position[i][j++] = CharToPiece(*p);
11351             }
11352         }
11353
11354         blackPlaysFirst = FALSE;
11355         if (!feof(f)) {
11356             (void) fgets(line, MSG_SIZ, f);
11357             if (strncmp(line, "black", strlen("black"))==0)
11358               blackPlaysFirst = TRUE;
11359         }
11360     }
11361     startedFromSetupPosition = TRUE;
11362
11363     SendToProgram("force\n", &first);
11364     CopyBoard(boards[0], initial_position);
11365     if (blackPlaysFirst) {
11366         currentMove = forwardMostMove = backwardMostMove = 1;
11367         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11368         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11369         CopyBoard(boards[1], initial_position);
11370         DisplayMessage("", _("Black to play"));
11371     } else {
11372         currentMove = forwardMostMove = backwardMostMove = 0;
11373         DisplayMessage("", _("White to play"));
11374     }
11375     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11376     SendBoard(&first, forwardMostMove);
11377     if (appData.debugMode) {
11378 int i, j;
11379   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11380   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11381         fprintf(debugFP, "Load Position\n");
11382     }
11383
11384     if (positionNumber > 1) {
11385       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11386         DisplayTitle(line);
11387     } else {
11388         DisplayTitle(title);
11389     }
11390     gameMode = EditGame;
11391     ModeHighlight();
11392     ResetClocks();
11393     timeRemaining[0][1] = whiteTimeRemaining;
11394     timeRemaining[1][1] = blackTimeRemaining;
11395     DrawPosition(FALSE, boards[currentMove]);
11396
11397     return TRUE;
11398 }
11399
11400
11401 void
11402 CopyPlayerNameIntoFileName(dest, src)
11403      char **dest, *src;
11404 {
11405     while (*src != NULLCHAR && *src != ',') {
11406         if (*src == ' ') {
11407             *(*dest)++ = '_';
11408             src++;
11409         } else {
11410             *(*dest)++ = *src++;
11411         }
11412     }
11413 }
11414
11415 char *DefaultFileName(ext)
11416      char *ext;
11417 {
11418     static char def[MSG_SIZ];
11419     char *p;
11420
11421     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11422         p = def;
11423         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11424         *p++ = '-';
11425         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11426         *p++ = '.';
11427         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11428     } else {
11429         def[0] = NULLCHAR;
11430     }
11431     return def;
11432 }
11433
11434 /* Save the current game to the given file */
11435 int
11436 SaveGameToFile(filename, append)
11437      char *filename;
11438      int append;
11439 {
11440     FILE *f;
11441     char buf[MSG_SIZ];
11442     int result;
11443
11444     if (strcmp(filename, "-") == 0) {
11445         return SaveGame(stdout, 0, NULL);
11446     } else {
11447         f = fopen(filename, append ? "a" : "w");
11448         if (f == NULL) {
11449             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11450             DisplayError(buf, errno);
11451             return FALSE;
11452         } else {
11453             safeStrCpy(buf, lastMsg, MSG_SIZ);
11454             DisplayMessage(_("Waiting for access to save file"), "");
11455             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11456             DisplayMessage(_("Saving game"), "");
11457             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11458             result = SaveGame(f, 0, NULL);
11459             DisplayMessage(buf, "");
11460             return result;
11461         }
11462     }
11463 }
11464
11465 char *
11466 SavePart(str)
11467      char *str;
11468 {
11469     static char buf[MSG_SIZ];
11470     char *p;
11471
11472     p = strchr(str, ' ');
11473     if (p == NULL) return str;
11474     strncpy(buf, str, p - str);
11475     buf[p - str] = NULLCHAR;
11476     return buf;
11477 }
11478
11479 #define PGN_MAX_LINE 75
11480
11481 #define PGN_SIDE_WHITE  0
11482 #define PGN_SIDE_BLACK  1
11483
11484 /* [AS] */
11485 static int FindFirstMoveOutOfBook( int side )
11486 {
11487     int result = -1;
11488
11489     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11490         int index = backwardMostMove;
11491         int has_book_hit = 0;
11492
11493         if( (index % 2) != side ) {
11494             index++;
11495         }
11496
11497         while( index < forwardMostMove ) {
11498             /* Check to see if engine is in book */
11499             int depth = pvInfoList[index].depth;
11500             int score = pvInfoList[index].score;
11501             int in_book = 0;
11502
11503             if( depth <= 2 ) {
11504                 in_book = 1;
11505             }
11506             else if( score == 0 && depth == 63 ) {
11507                 in_book = 1; /* Zappa */
11508             }
11509             else if( score == 2 && depth == 99 ) {
11510                 in_book = 1; /* Abrok */
11511             }
11512
11513             has_book_hit += in_book;
11514
11515             if( ! in_book ) {
11516                 result = index;
11517
11518                 break;
11519             }
11520
11521             index += 2;
11522         }
11523     }
11524
11525     return result;
11526 }
11527
11528 /* [AS] */
11529 void GetOutOfBookInfo( char * buf )
11530 {
11531     int oob[2];
11532     int i;
11533     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11534
11535     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11536     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11537
11538     *buf = '\0';
11539
11540     if( oob[0] >= 0 || oob[1] >= 0 ) {
11541         for( i=0; i<2; i++ ) {
11542             int idx = oob[i];
11543
11544             if( idx >= 0 ) {
11545                 if( i > 0 && oob[0] >= 0 ) {
11546                     strcat( buf, "   " );
11547                 }
11548
11549                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11550                 sprintf( buf+strlen(buf), "%s%.2f",
11551                     pvInfoList[idx].score >= 0 ? "+" : "",
11552                     pvInfoList[idx].score / 100.0 );
11553             }
11554         }
11555     }
11556 }
11557
11558 /* Save game in PGN style and close the file */
11559 int
11560 SaveGamePGN(f)
11561      FILE *f;
11562 {
11563     int i, offset, linelen, newblock;
11564     time_t tm;
11565 //    char *movetext;
11566     char numtext[32];
11567     int movelen, numlen, blank;
11568     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11569
11570     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11571
11572     tm = time((time_t *) NULL);
11573
11574     PrintPGNTags(f, &gameInfo);
11575
11576     if (backwardMostMove > 0 || startedFromSetupPosition) {
11577         char *fen = PositionToFEN(backwardMostMove, NULL);
11578         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11579         fprintf(f, "\n{--------------\n");
11580         PrintPosition(f, backwardMostMove);
11581         fprintf(f, "--------------}\n");
11582         free(fen);
11583     }
11584     else {
11585         /* [AS] Out of book annotation */
11586         if( appData.saveOutOfBookInfo ) {
11587             char buf[64];
11588
11589             GetOutOfBookInfo( buf );
11590
11591             if( buf[0] != '\0' ) {
11592                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11593             }
11594         }
11595
11596         fprintf(f, "\n");
11597     }
11598
11599     i = backwardMostMove;
11600     linelen = 0;
11601     newblock = TRUE;
11602
11603     while (i < forwardMostMove) {
11604         /* Print comments preceding this move */
11605         if (commentList[i] != NULL) {
11606             if (linelen > 0) fprintf(f, "\n");
11607             fprintf(f, "%s", commentList[i]);
11608             linelen = 0;
11609             newblock = TRUE;
11610         }
11611
11612         /* Format move number */
11613         if ((i % 2) == 0)
11614           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11615         else
11616           if (newblock)
11617             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11618           else
11619             numtext[0] = NULLCHAR;
11620
11621         numlen = strlen(numtext);
11622         newblock = FALSE;
11623
11624         /* Print move number */
11625         blank = linelen > 0 && numlen > 0;
11626         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11627             fprintf(f, "\n");
11628             linelen = 0;
11629             blank = 0;
11630         }
11631         if (blank) {
11632             fprintf(f, " ");
11633             linelen++;
11634         }
11635         fprintf(f, "%s", numtext);
11636         linelen += numlen;
11637
11638         /* Get move */
11639         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11640         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11641
11642         /* Print move */
11643         blank = linelen > 0 && movelen > 0;
11644         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11645             fprintf(f, "\n");
11646             linelen = 0;
11647             blank = 0;
11648         }
11649         if (blank) {
11650             fprintf(f, " ");
11651             linelen++;
11652         }
11653         fprintf(f, "%s", move_buffer);
11654         linelen += movelen;
11655
11656         /* [AS] Add PV info if present */
11657         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11658             /* [HGM] add time */
11659             char buf[MSG_SIZ]; int seconds;
11660
11661             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11662
11663             if( seconds <= 0)
11664               buf[0] = 0;
11665             else
11666               if( seconds < 30 )
11667                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11668               else
11669                 {
11670                   seconds = (seconds + 4)/10; // round to full seconds
11671                   if( seconds < 60 )
11672                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11673                   else
11674                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11675                 }
11676
11677             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11678                       pvInfoList[i].score >= 0 ? "+" : "",
11679                       pvInfoList[i].score / 100.0,
11680                       pvInfoList[i].depth,
11681                       buf );
11682
11683             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11684
11685             /* Print score/depth */
11686             blank = linelen > 0 && movelen > 0;
11687             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11688                 fprintf(f, "\n");
11689                 linelen = 0;
11690                 blank = 0;
11691             }
11692             if (blank) {
11693                 fprintf(f, " ");
11694                 linelen++;
11695             }
11696             fprintf(f, "%s", move_buffer);
11697             linelen += movelen;
11698         }
11699
11700         i++;
11701     }
11702
11703     /* Start a new line */
11704     if (linelen > 0) fprintf(f, "\n");
11705
11706     /* Print comments after last move */
11707     if (commentList[i] != NULL) {
11708         fprintf(f, "%s\n", commentList[i]);
11709     }
11710
11711     /* Print result */
11712     if (gameInfo.resultDetails != NULL &&
11713         gameInfo.resultDetails[0] != NULLCHAR) {
11714         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11715                 PGNResult(gameInfo.result));
11716     } else {
11717         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11718     }
11719
11720     fclose(f);
11721     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11722     return TRUE;
11723 }
11724
11725 /* Save game in old style and close the file */
11726 int
11727 SaveGameOldStyle(f)
11728      FILE *f;
11729 {
11730     int i, offset;
11731     time_t tm;
11732
11733     tm = time((time_t *) NULL);
11734
11735     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11736     PrintOpponents(f);
11737
11738     if (backwardMostMove > 0 || startedFromSetupPosition) {
11739         fprintf(f, "\n[--------------\n");
11740         PrintPosition(f, backwardMostMove);
11741         fprintf(f, "--------------]\n");
11742     } else {
11743         fprintf(f, "\n");
11744     }
11745
11746     i = backwardMostMove;
11747     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11748
11749     while (i < forwardMostMove) {
11750         if (commentList[i] != NULL) {
11751             fprintf(f, "[%s]\n", commentList[i]);
11752         }
11753
11754         if ((i % 2) == 1) {
11755             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11756             i++;
11757         } else {
11758             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11759             i++;
11760             if (commentList[i] != NULL) {
11761                 fprintf(f, "\n");
11762                 continue;
11763             }
11764             if (i >= forwardMostMove) {
11765                 fprintf(f, "\n");
11766                 break;
11767             }
11768             fprintf(f, "%s\n", parseList[i]);
11769             i++;
11770         }
11771     }
11772
11773     if (commentList[i] != NULL) {
11774         fprintf(f, "[%s]\n", commentList[i]);
11775     }
11776
11777     /* This isn't really the old style, but it's close enough */
11778     if (gameInfo.resultDetails != NULL &&
11779         gameInfo.resultDetails[0] != NULLCHAR) {
11780         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11781                 gameInfo.resultDetails);
11782     } else {
11783         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11784     }
11785
11786     fclose(f);
11787     return TRUE;
11788 }
11789
11790 /* Save the current game to open file f and close the file */
11791 int
11792 SaveGame(f, dummy, dummy2)
11793      FILE *f;
11794      int dummy;
11795      char *dummy2;
11796 {
11797     if (gameMode == EditPosition) EditPositionDone(TRUE);
11798     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11799     if (appData.oldSaveStyle)
11800       return SaveGameOldStyle(f);
11801     else
11802       return SaveGamePGN(f);
11803 }
11804
11805 /* Save the current position to the given file */
11806 int
11807 SavePositionToFile(filename)
11808      char *filename;
11809 {
11810     FILE *f;
11811     char buf[MSG_SIZ];
11812
11813     if (strcmp(filename, "-") == 0) {
11814         return SavePosition(stdout, 0, NULL);
11815     } else {
11816         f = fopen(filename, "a");
11817         if (f == NULL) {
11818             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11819             DisplayError(buf, errno);
11820             return FALSE;
11821         } else {
11822             safeStrCpy(buf, lastMsg, MSG_SIZ);
11823             DisplayMessage(_("Waiting for access to save file"), "");
11824             flock(fileno(f), LOCK_EX); // [HGM] lock
11825             DisplayMessage(_("Saving position"), "");
11826             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11827             SavePosition(f, 0, NULL);
11828             DisplayMessage(buf, "");
11829             return TRUE;
11830         }
11831     }
11832 }
11833
11834 /* Save the current position to the given open file and close the file */
11835 int
11836 SavePosition(f, dummy, dummy2)
11837      FILE *f;
11838      int dummy;
11839      char *dummy2;
11840 {
11841     time_t tm;
11842     char *fen;
11843
11844     if (gameMode == EditPosition) EditPositionDone(TRUE);
11845     if (appData.oldSaveStyle) {
11846         tm = time((time_t *) NULL);
11847
11848         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11849         PrintOpponents(f);
11850         fprintf(f, "[--------------\n");
11851         PrintPosition(f, currentMove);
11852         fprintf(f, "--------------]\n");
11853     } else {
11854         fen = PositionToFEN(currentMove, NULL);
11855         fprintf(f, "%s\n", fen);
11856         free(fen);
11857     }
11858     fclose(f);
11859     return TRUE;
11860 }
11861
11862 void
11863 ReloadCmailMsgEvent(unregister)
11864      int unregister;
11865 {
11866 #if !WIN32
11867     static char *inFilename = NULL;
11868     static char *outFilename;
11869     int i;
11870     struct stat inbuf, outbuf;
11871     int status;
11872
11873     /* Any registered moves are unregistered if unregister is set, */
11874     /* i.e. invoked by the signal handler */
11875     if (unregister) {
11876         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11877             cmailMoveRegistered[i] = FALSE;
11878             if (cmailCommentList[i] != NULL) {
11879                 free(cmailCommentList[i]);
11880                 cmailCommentList[i] = NULL;
11881             }
11882         }
11883         nCmailMovesRegistered = 0;
11884     }
11885
11886     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11887         cmailResult[i] = CMAIL_NOT_RESULT;
11888     }
11889     nCmailResults = 0;
11890
11891     if (inFilename == NULL) {
11892         /* Because the filenames are static they only get malloced once  */
11893         /* and they never get freed                                      */
11894         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11895         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11896
11897         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11898         sprintf(outFilename, "%s.out", appData.cmailGameName);
11899     }
11900
11901     status = stat(outFilename, &outbuf);
11902     if (status < 0) {
11903         cmailMailedMove = FALSE;
11904     } else {
11905         status = stat(inFilename, &inbuf);
11906         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11907     }
11908
11909     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11910        counts the games, notes how each one terminated, etc.
11911
11912        It would be nice to remove this kludge and instead gather all
11913        the information while building the game list.  (And to keep it
11914        in the game list nodes instead of having a bunch of fixed-size
11915        parallel arrays.)  Note this will require getting each game's
11916        termination from the PGN tags, as the game list builder does
11917        not process the game moves.  --mann
11918        */
11919     cmailMsgLoaded = TRUE;
11920     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11921
11922     /* Load first game in the file or popup game menu */
11923     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11924
11925 #endif /* !WIN32 */
11926     return;
11927 }
11928
11929 int
11930 RegisterMove()
11931 {
11932     FILE *f;
11933     char string[MSG_SIZ];
11934
11935     if (   cmailMailedMove
11936         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11937         return TRUE;            /* Allow free viewing  */
11938     }
11939
11940     /* Unregister move to ensure that we don't leave RegisterMove        */
11941     /* with the move registered when the conditions for registering no   */
11942     /* longer hold                                                       */
11943     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11944         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11945         nCmailMovesRegistered --;
11946
11947         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11948           {
11949               free(cmailCommentList[lastLoadGameNumber - 1]);
11950               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11951           }
11952     }
11953
11954     if (cmailOldMove == -1) {
11955         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11956         return FALSE;
11957     }
11958
11959     if (currentMove > cmailOldMove + 1) {
11960         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11961         return FALSE;
11962     }
11963
11964     if (currentMove < cmailOldMove) {
11965         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11966         return FALSE;
11967     }
11968
11969     if (forwardMostMove > currentMove) {
11970         /* Silently truncate extra moves */
11971         TruncateGame();
11972     }
11973
11974     if (   (currentMove == cmailOldMove + 1)
11975         || (   (currentMove == cmailOldMove)
11976             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11977                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11978         if (gameInfo.result != GameUnfinished) {
11979             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11980         }
11981
11982         if (commentList[currentMove] != NULL) {
11983             cmailCommentList[lastLoadGameNumber - 1]
11984               = StrSave(commentList[currentMove]);
11985         }
11986         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11987
11988         if (appData.debugMode)
11989           fprintf(debugFP, "Saving %s for game %d\n",
11990                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11991
11992         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11993
11994         f = fopen(string, "w");
11995         if (appData.oldSaveStyle) {
11996             SaveGameOldStyle(f); /* also closes the file */
11997
11998             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11999             f = fopen(string, "w");
12000             SavePosition(f, 0, NULL); /* also closes the file */
12001         } else {
12002             fprintf(f, "{--------------\n");
12003             PrintPosition(f, currentMove);
12004             fprintf(f, "--------------}\n\n");
12005
12006             SaveGame(f, 0, NULL); /* also closes the file*/
12007         }
12008
12009         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12010         nCmailMovesRegistered ++;
12011     } else if (nCmailGames == 1) {
12012         DisplayError(_("You have not made a move yet"), 0);
12013         return FALSE;
12014     }
12015
12016     return TRUE;
12017 }
12018
12019 void
12020 MailMoveEvent()
12021 {
12022 #if !WIN32
12023     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12024     FILE *commandOutput;
12025     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12026     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12027     int nBuffers;
12028     int i;
12029     int archived;
12030     char *arcDir;
12031
12032     if (! cmailMsgLoaded) {
12033         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12034         return;
12035     }
12036
12037     if (nCmailGames == nCmailResults) {
12038         DisplayError(_("No unfinished games"), 0);
12039         return;
12040     }
12041
12042 #if CMAIL_PROHIBIT_REMAIL
12043     if (cmailMailedMove) {
12044       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);
12045         DisplayError(msg, 0);
12046         return;
12047     }
12048 #endif
12049
12050     if (! (cmailMailedMove || RegisterMove())) return;
12051
12052     if (   cmailMailedMove
12053         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12054       snprintf(string, MSG_SIZ, partCommandString,
12055                appData.debugMode ? " -v" : "", appData.cmailGameName);
12056         commandOutput = popen(string, "r");
12057
12058         if (commandOutput == NULL) {
12059             DisplayError(_("Failed to invoke cmail"), 0);
12060         } else {
12061             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12062                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12063             }
12064             if (nBuffers > 1) {
12065                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12066                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12067                 nBytes = MSG_SIZ - 1;
12068             } else {
12069                 (void) memcpy(msg, buffer, nBytes);
12070             }
12071             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12072
12073             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12074                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12075
12076                 archived = TRUE;
12077                 for (i = 0; i < nCmailGames; i ++) {
12078                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12079                         archived = FALSE;
12080                     }
12081                 }
12082                 if (   archived
12083                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12084                         != NULL)) {
12085                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12086                            arcDir,
12087                            appData.cmailGameName,
12088                            gameInfo.date);
12089                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12090                     cmailMsgLoaded = FALSE;
12091                 }
12092             }
12093
12094             DisplayInformation(msg);
12095             pclose(commandOutput);
12096         }
12097     } else {
12098         if ((*cmailMsg) != '\0') {
12099             DisplayInformation(cmailMsg);
12100         }
12101     }
12102
12103     return;
12104 #endif /* !WIN32 */
12105 }
12106
12107 char *
12108 CmailMsg()
12109 {
12110 #if WIN32
12111     return NULL;
12112 #else
12113     int  prependComma = 0;
12114     char number[5];
12115     char string[MSG_SIZ];       /* Space for game-list */
12116     int  i;
12117
12118     if (!cmailMsgLoaded) return "";
12119
12120     if (cmailMailedMove) {
12121       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12122     } else {
12123         /* Create a list of games left */
12124       snprintf(string, MSG_SIZ, "[");
12125         for (i = 0; i < nCmailGames; i ++) {
12126             if (! (   cmailMoveRegistered[i]
12127                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12128                 if (prependComma) {
12129                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12130                 } else {
12131                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12132                     prependComma = 1;
12133                 }
12134
12135                 strcat(string, number);
12136             }
12137         }
12138         strcat(string, "]");
12139
12140         if (nCmailMovesRegistered + nCmailResults == 0) {
12141             switch (nCmailGames) {
12142               case 1:
12143                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12144                 break;
12145
12146               case 2:
12147                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12148                 break;
12149
12150               default:
12151                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12152                          nCmailGames);
12153                 break;
12154             }
12155         } else {
12156             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12157               case 1:
12158                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12159                          string);
12160                 break;
12161
12162               case 0:
12163                 if (nCmailResults == nCmailGames) {
12164                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12165                 } else {
12166                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12167                 }
12168                 break;
12169
12170               default:
12171                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12172                          string);
12173             }
12174         }
12175     }
12176     return cmailMsg;
12177 #endif /* WIN32 */
12178 }
12179
12180 void
12181 ResetGameEvent()
12182 {
12183     if (gameMode == Training)
12184       SetTrainingModeOff();
12185
12186     Reset(TRUE, TRUE);
12187     cmailMsgLoaded = FALSE;
12188     if (appData.icsActive) {
12189       SendToICS(ics_prefix);
12190       SendToICS("refresh\n");
12191     }
12192 }
12193
12194 void
12195 ExitEvent(status)
12196      int status;
12197 {
12198     exiting++;
12199     if (exiting > 2) {
12200       /* Give up on clean exit */
12201       exit(status);
12202     }
12203     if (exiting > 1) {
12204       /* Keep trying for clean exit */
12205       return;
12206     }
12207
12208     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12209
12210     if (telnetISR != NULL) {
12211       RemoveInputSource(telnetISR);
12212     }
12213     if (icsPR != NoProc) {
12214       DestroyChildProcess(icsPR, TRUE);
12215     }
12216
12217     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12218     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12219
12220     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12221     /* make sure this other one finishes before killing it!                  */
12222     if(endingGame) { int count = 0;
12223         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12224         while(endingGame && count++ < 10) DoSleep(1);
12225         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12226     }
12227
12228     /* Kill off chess programs */
12229     if (first.pr != NoProc) {
12230         ExitAnalyzeMode();
12231
12232         DoSleep( appData.delayBeforeQuit );
12233         SendToProgram("quit\n", &first);
12234         DoSleep( appData.delayAfterQuit );
12235         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12236     }
12237     if (second.pr != NoProc) {
12238         DoSleep( appData.delayBeforeQuit );
12239         SendToProgram("quit\n", &second);
12240         DoSleep( appData.delayAfterQuit );
12241         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12242     }
12243     if (first.isr != NULL) {
12244         RemoveInputSource(first.isr);
12245     }
12246     if (second.isr != NULL) {
12247         RemoveInputSource(second.isr);
12248     }
12249
12250     ShutDownFrontEnd();
12251     exit(status);
12252 }
12253
12254 void
12255 PauseEvent()
12256 {
12257     if (appData.debugMode)
12258         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12259     if (pausing) {
12260         pausing = FALSE;
12261         ModeHighlight();
12262         if (gameMode == MachinePlaysWhite ||
12263             gameMode == MachinePlaysBlack) {
12264             StartClocks();
12265         } else {
12266             DisplayBothClocks();
12267         }
12268         if (gameMode == PlayFromGameFile) {
12269             if (appData.timeDelay >= 0)
12270                 AutoPlayGameLoop();
12271         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12272             Reset(FALSE, TRUE);
12273             SendToICS(ics_prefix);
12274             SendToICS("refresh\n");
12275         } else if (currentMove < forwardMostMove) {
12276             ForwardInner(forwardMostMove);
12277         }
12278         pauseExamInvalid = FALSE;
12279     } else {
12280         switch (gameMode) {
12281           default:
12282             return;
12283           case IcsExamining:
12284             pauseExamForwardMostMove = forwardMostMove;
12285             pauseExamInvalid = FALSE;
12286             /* fall through */
12287           case IcsObserving:
12288           case IcsPlayingWhite:
12289           case IcsPlayingBlack:
12290             pausing = TRUE;
12291             ModeHighlight();
12292             return;
12293           case PlayFromGameFile:
12294             (void) StopLoadGameTimer();
12295             pausing = TRUE;
12296             ModeHighlight();
12297             break;
12298           case BeginningOfGame:
12299             if (appData.icsActive) return;
12300             /* else fall through */
12301           case MachinePlaysWhite:
12302           case MachinePlaysBlack:
12303           case TwoMachinesPlay:
12304             if (forwardMostMove == 0)
12305               return;           /* don't pause if no one has moved */
12306             if ((gameMode == MachinePlaysWhite &&
12307                  !WhiteOnMove(forwardMostMove)) ||
12308                 (gameMode == MachinePlaysBlack &&
12309                  WhiteOnMove(forwardMostMove))) {
12310                 StopClocks();
12311             }
12312             pausing = TRUE;
12313             ModeHighlight();
12314             break;
12315         }
12316     }
12317 }
12318
12319 void
12320 EditCommentEvent()
12321 {
12322     char title[MSG_SIZ];
12323
12324     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12325       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12326     } else {
12327       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12328                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12329                parseList[currentMove - 1]);
12330     }
12331
12332     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12333 }
12334
12335
12336 void
12337 EditTagsEvent()
12338 {
12339     char *tags = PGNTags(&gameInfo);
12340     bookUp = FALSE;
12341     EditTagsPopUp(tags, NULL);
12342     free(tags);
12343 }
12344
12345 void
12346 AnalyzeModeEvent()
12347 {
12348     if (appData.noChessProgram || gameMode == AnalyzeMode)
12349       return;
12350
12351     if (gameMode != AnalyzeFile) {
12352         if (!appData.icsEngineAnalyze) {
12353                EditGameEvent();
12354                if (gameMode != EditGame) return;
12355         }
12356         ResurrectChessProgram();
12357         SendToProgram("analyze\n", &first);
12358         first.analyzing = TRUE;
12359         /*first.maybeThinking = TRUE;*/
12360         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12361         EngineOutputPopUp();
12362     }
12363     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12364     pausing = FALSE;
12365     ModeHighlight();
12366     SetGameInfo();
12367
12368     StartAnalysisClock();
12369     GetTimeMark(&lastNodeCountTime);
12370     lastNodeCount = 0;
12371 }
12372
12373 void
12374 AnalyzeFileEvent()
12375 {
12376     if (appData.noChessProgram || gameMode == AnalyzeFile)
12377       return;
12378
12379     if (gameMode != AnalyzeMode) {
12380         EditGameEvent();
12381         if (gameMode != EditGame) return;
12382         ResurrectChessProgram();
12383         SendToProgram("analyze\n", &first);
12384         first.analyzing = TRUE;
12385         /*first.maybeThinking = TRUE;*/
12386         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12387         EngineOutputPopUp();
12388     }
12389     gameMode = AnalyzeFile;
12390     pausing = FALSE;
12391     ModeHighlight();
12392     SetGameInfo();
12393
12394     StartAnalysisClock();
12395     GetTimeMark(&lastNodeCountTime);
12396     lastNodeCount = 0;
12397 }
12398
12399 void
12400 MachineWhiteEvent()
12401 {
12402     char buf[MSG_SIZ];
12403     char *bookHit = NULL;
12404
12405     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12406       return;
12407
12408
12409     if (gameMode == PlayFromGameFile ||
12410         gameMode == TwoMachinesPlay  ||
12411         gameMode == Training         ||
12412         gameMode == AnalyzeMode      ||
12413         gameMode == EndOfGame)
12414         EditGameEvent();
12415
12416     if (gameMode == EditPosition)
12417         EditPositionDone(TRUE);
12418
12419     if (!WhiteOnMove(currentMove)) {
12420         DisplayError(_("It is not White's turn"), 0);
12421         return;
12422     }
12423
12424     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12425       ExitAnalyzeMode();
12426
12427     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12428         gameMode == AnalyzeFile)
12429         TruncateGame();
12430
12431     ResurrectChessProgram();    /* in case it isn't running */
12432     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12433         gameMode = MachinePlaysWhite;
12434         ResetClocks();
12435     } else
12436     gameMode = MachinePlaysWhite;
12437     pausing = FALSE;
12438     ModeHighlight();
12439     SetGameInfo();
12440     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12441     DisplayTitle(buf);
12442     if (first.sendName) {
12443       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12444       SendToProgram(buf, &first);
12445     }
12446     if (first.sendTime) {
12447       if (first.useColors) {
12448         SendToProgram("black\n", &first); /*gnu kludge*/
12449       }
12450       SendTimeRemaining(&first, TRUE);
12451     }
12452     if (first.useColors) {
12453       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12454     }
12455     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12456     SetMachineThinkingEnables();
12457     first.maybeThinking = TRUE;
12458     StartClocks();
12459     firstMove = FALSE;
12460
12461     if (appData.autoFlipView && !flipView) {
12462       flipView = !flipView;
12463       DrawPosition(FALSE, NULL);
12464       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12465     }
12466
12467     if(bookHit) { // [HGM] book: simulate book reply
12468         static char bookMove[MSG_SIZ]; // a bit generous?
12469
12470         programStats.nodes = programStats.depth = programStats.time =
12471         programStats.score = programStats.got_only_move = 0;
12472         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12473
12474         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12475         strcat(bookMove, bookHit);
12476         HandleMachineMove(bookMove, &first);
12477     }
12478 }
12479
12480 void
12481 MachineBlackEvent()
12482 {
12483   char buf[MSG_SIZ];
12484   char *bookHit = NULL;
12485
12486     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12487         return;
12488
12489
12490     if (gameMode == PlayFromGameFile ||
12491         gameMode == TwoMachinesPlay  ||
12492         gameMode == Training         ||
12493         gameMode == AnalyzeMode      ||
12494         gameMode == EndOfGame)
12495         EditGameEvent();
12496
12497     if (gameMode == EditPosition)
12498         EditPositionDone(TRUE);
12499
12500     if (WhiteOnMove(currentMove)) {
12501         DisplayError(_("It is not Black's turn"), 0);
12502         return;
12503     }
12504
12505     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12506       ExitAnalyzeMode();
12507
12508     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12509         gameMode == AnalyzeFile)
12510         TruncateGame();
12511
12512     ResurrectChessProgram();    /* in case it isn't running */
12513     gameMode = MachinePlaysBlack;
12514     pausing = FALSE;
12515     ModeHighlight();
12516     SetGameInfo();
12517     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12518     DisplayTitle(buf);
12519     if (first.sendName) {
12520       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12521       SendToProgram(buf, &first);
12522     }
12523     if (first.sendTime) {
12524       if (first.useColors) {
12525         SendToProgram("white\n", &first); /*gnu kludge*/
12526       }
12527       SendTimeRemaining(&first, FALSE);
12528     }
12529     if (first.useColors) {
12530       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12531     }
12532     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12533     SetMachineThinkingEnables();
12534     first.maybeThinking = TRUE;
12535     StartClocks();
12536
12537     if (appData.autoFlipView && flipView) {
12538       flipView = !flipView;
12539       DrawPosition(FALSE, NULL);
12540       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12541     }
12542     if(bookHit) { // [HGM] book: simulate book reply
12543         static char bookMove[MSG_SIZ]; // a bit generous?
12544
12545         programStats.nodes = programStats.depth = programStats.time =
12546         programStats.score = programStats.got_only_move = 0;
12547         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12548
12549         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12550         strcat(bookMove, bookHit);
12551         HandleMachineMove(bookMove, &first);
12552     }
12553 }
12554
12555
12556 void
12557 DisplayTwoMachinesTitle()
12558 {
12559     char buf[MSG_SIZ];
12560     if (appData.matchGames > 0) {
12561         if (first.twoMachinesColor[0] == 'w') {
12562           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12563                    gameInfo.white, gameInfo.black,
12564                    first.matchWins, second.matchWins,
12565                    matchGame - 1 - (first.matchWins + second.matchWins));
12566         } else {
12567           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12568                    gameInfo.white, gameInfo.black,
12569                    second.matchWins, first.matchWins,
12570                    matchGame - 1 - (first.matchWins + second.matchWins));
12571         }
12572     } else {
12573       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12574     }
12575     DisplayTitle(buf);
12576 }
12577
12578 void
12579 SettingsMenuIfReady()
12580 {
12581   if (second.lastPing != second.lastPong) {
12582     DisplayMessage("", _("Waiting for second chess program"));
12583     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12584     return;
12585   }
12586   ThawUI();
12587   DisplayMessage("", "");
12588   SettingsPopUp(&second);
12589 }
12590
12591 int
12592 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12593 {
12594     char buf[MSG_SIZ];
12595     if (cps->pr == NULL) {
12596         StartChessProgram(cps);
12597         if (cps->protocolVersion == 1) {
12598           retry();
12599         } else {
12600           /* kludge: allow timeout for initial "feature" command */
12601           FreezeUI();
12602           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12603           DisplayMessage("", buf);
12604           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12605         }
12606         return 1;
12607     }
12608     return 0;
12609 }
12610
12611 void
12612 TwoMachinesEvent P((void))
12613 {
12614     int i;
12615     char buf[MSG_SIZ];
12616     ChessProgramState *onmove;
12617     char *bookHit = NULL;
12618     static int stalling = 0;
12619     TimeMark now;
12620     long wait;
12621
12622     if (appData.noChessProgram) return;
12623
12624     switch (gameMode) {
12625       case TwoMachinesPlay:
12626         return;
12627       case MachinePlaysWhite:
12628       case MachinePlaysBlack:
12629         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12630             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12631             return;
12632         }
12633         /* fall through */
12634       case BeginningOfGame:
12635       case PlayFromGameFile:
12636       case EndOfGame:
12637         EditGameEvent();
12638         if (gameMode != EditGame) return;
12639         break;
12640       case EditPosition:
12641         EditPositionDone(TRUE);
12642         break;
12643       case AnalyzeMode:
12644       case AnalyzeFile:
12645         ExitAnalyzeMode();
12646         break;
12647       case EditGame:
12648       default:
12649         break;
12650     }
12651
12652 //    forwardMostMove = currentMove;
12653     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12654
12655     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12656
12657     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12658     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12659       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12660       return;
12661     }
12662     if(!stalling) {
12663       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12664       SendToProgram("force\n", &second);
12665       stalling = 1;
12666       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12667       return;
12668     }
12669     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12670     if(appData.matchPause>10000 || appData.matchPause<10)
12671                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12672     wait = SubtractTimeMarks(&now, &pauseStart);
12673     if(wait < appData.matchPause) {
12674         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12675         return;
12676     }
12677     stalling = 0;
12678     DisplayMessage("", "");
12679     if (startedFromSetupPosition) {
12680         SendBoard(&second, backwardMostMove);
12681     if (appData.debugMode) {
12682         fprintf(debugFP, "Two Machines\n");
12683     }
12684     }
12685     for (i = backwardMostMove; i < forwardMostMove; i++) {
12686         SendMoveToProgram(i, &second);
12687     }
12688
12689     gameMode = TwoMachinesPlay;
12690     pausing = FALSE;
12691     ModeHighlight();
12692     SetGameInfo();
12693     DisplayTwoMachinesTitle();
12694     firstMove = TRUE;
12695     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12696         onmove = &first;
12697     } else {
12698         onmove = &second;
12699     }
12700     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12701     SendToProgram(first.computerString, &first);
12702     if (first.sendName) {
12703       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12704       SendToProgram(buf, &first);
12705     }
12706     SendToProgram(second.computerString, &second);
12707     if (second.sendName) {
12708       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12709       SendToProgram(buf, &second);
12710     }
12711
12712     ResetClocks();
12713     if (!first.sendTime || !second.sendTime) {
12714         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12715         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12716     }
12717     if (onmove->sendTime) {
12718       if (onmove->useColors) {
12719         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12720       }
12721       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12722     }
12723     if (onmove->useColors) {
12724       SendToProgram(onmove->twoMachinesColor, onmove);
12725     }
12726     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12727 //    SendToProgram("go\n", onmove);
12728     onmove->maybeThinking = TRUE;
12729     SetMachineThinkingEnables();
12730
12731     StartClocks();
12732
12733     if(bookHit) { // [HGM] book: simulate book reply
12734         static char bookMove[MSG_SIZ]; // a bit generous?
12735
12736         programStats.nodes = programStats.depth = programStats.time =
12737         programStats.score = programStats.got_only_move = 0;
12738         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12739
12740         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12741         strcat(bookMove, bookHit);
12742         savedMessage = bookMove; // args for deferred call
12743         savedState = onmove;
12744         ScheduleDelayedEvent(DeferredBookMove, 1);
12745     }
12746 }
12747
12748 void
12749 TrainingEvent()
12750 {
12751     if (gameMode == Training) {
12752       SetTrainingModeOff();
12753       gameMode = PlayFromGameFile;
12754       DisplayMessage("", _("Training mode off"));
12755     } else {
12756       gameMode = Training;
12757       animateTraining = appData.animate;
12758
12759       /* make sure we are not already at the end of the game */
12760       if (currentMove < forwardMostMove) {
12761         SetTrainingModeOn();
12762         DisplayMessage("", _("Training mode on"));
12763       } else {
12764         gameMode = PlayFromGameFile;
12765         DisplayError(_("Already at end of game"), 0);
12766       }
12767     }
12768     ModeHighlight();
12769 }
12770
12771 void
12772 IcsClientEvent()
12773 {
12774     if (!appData.icsActive) return;
12775     switch (gameMode) {
12776       case IcsPlayingWhite:
12777       case IcsPlayingBlack:
12778       case IcsObserving:
12779       case IcsIdle:
12780       case BeginningOfGame:
12781       case IcsExamining:
12782         return;
12783
12784       case EditGame:
12785         break;
12786
12787       case EditPosition:
12788         EditPositionDone(TRUE);
12789         break;
12790
12791       case AnalyzeMode:
12792       case AnalyzeFile:
12793         ExitAnalyzeMode();
12794         break;
12795
12796       default:
12797         EditGameEvent();
12798         break;
12799     }
12800
12801     gameMode = IcsIdle;
12802     ModeHighlight();
12803     return;
12804 }
12805
12806
12807 void
12808 EditGameEvent()
12809 {
12810     int i;
12811
12812     switch (gameMode) {
12813       case Training:
12814         SetTrainingModeOff();
12815         break;
12816       case MachinePlaysWhite:
12817       case MachinePlaysBlack:
12818       case BeginningOfGame:
12819         SendToProgram("force\n", &first);
12820         SetUserThinkingEnables();
12821         break;
12822       case PlayFromGameFile:
12823         (void) StopLoadGameTimer();
12824         if (gameFileFP != NULL) {
12825             gameFileFP = NULL;
12826         }
12827         break;
12828       case EditPosition:
12829         EditPositionDone(TRUE);
12830         break;
12831       case AnalyzeMode:
12832       case AnalyzeFile:
12833         ExitAnalyzeMode();
12834         SendToProgram("force\n", &first);
12835         break;
12836       case TwoMachinesPlay:
12837         GameEnds(EndOfFile, NULL, GE_PLAYER);
12838         ResurrectChessProgram();
12839         SetUserThinkingEnables();
12840         break;
12841       case EndOfGame:
12842         ResurrectChessProgram();
12843         break;
12844       case IcsPlayingBlack:
12845       case IcsPlayingWhite:
12846         DisplayError(_("Warning: You are still playing a game"), 0);
12847         break;
12848       case IcsObserving:
12849         DisplayError(_("Warning: You are still observing a game"), 0);
12850         break;
12851       case IcsExamining:
12852         DisplayError(_("Warning: You are still examining a game"), 0);
12853         break;
12854       case IcsIdle:
12855         break;
12856       case EditGame:
12857       default:
12858         return;
12859     }
12860
12861     pausing = FALSE;
12862     StopClocks();
12863     first.offeredDraw = second.offeredDraw = 0;
12864
12865     if (gameMode == PlayFromGameFile) {
12866         whiteTimeRemaining = timeRemaining[0][currentMove];
12867         blackTimeRemaining = timeRemaining[1][currentMove];
12868         DisplayTitle("");
12869     }
12870
12871     if (gameMode == MachinePlaysWhite ||
12872         gameMode == MachinePlaysBlack ||
12873         gameMode == TwoMachinesPlay ||
12874         gameMode == EndOfGame) {
12875         i = forwardMostMove;
12876         while (i > currentMove) {
12877             SendToProgram("undo\n", &first);
12878             i--;
12879         }
12880         whiteTimeRemaining = timeRemaining[0][currentMove];
12881         blackTimeRemaining = timeRemaining[1][currentMove];
12882         DisplayBothClocks();
12883         if (whiteFlag || blackFlag) {
12884             whiteFlag = blackFlag = 0;
12885         }
12886         DisplayTitle("");
12887     }
12888
12889     gameMode = EditGame;
12890     ModeHighlight();
12891     SetGameInfo();
12892 }
12893
12894
12895 void
12896 EditPositionEvent()
12897 {
12898     if (gameMode == EditPosition) {
12899         EditGameEvent();
12900         return;
12901     }
12902
12903     EditGameEvent();
12904     if (gameMode != EditGame) return;
12905
12906     gameMode = EditPosition;
12907     ModeHighlight();
12908     SetGameInfo();
12909     if (currentMove > 0)
12910       CopyBoard(boards[0], boards[currentMove]);
12911
12912     blackPlaysFirst = !WhiteOnMove(currentMove);
12913     ResetClocks();
12914     currentMove = forwardMostMove = backwardMostMove = 0;
12915     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12916     DisplayMove(-1);
12917 }
12918
12919 void
12920 ExitAnalyzeMode()
12921 {
12922     /* [DM] icsEngineAnalyze - possible call from other functions */
12923     if (appData.icsEngineAnalyze) {
12924         appData.icsEngineAnalyze = FALSE;
12925
12926         DisplayMessage("",_("Close ICS engine analyze..."));
12927     }
12928     if (first.analysisSupport && first.analyzing) {
12929       SendToProgram("exit\n", &first);
12930       first.analyzing = FALSE;
12931     }
12932     thinkOutput[0] = NULLCHAR;
12933 }
12934
12935 void
12936 EditPositionDone(Boolean fakeRights)
12937 {
12938     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12939
12940     startedFromSetupPosition = TRUE;
12941     InitChessProgram(&first, FALSE);
12942     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12943       boards[0][EP_STATUS] = EP_NONE;
12944       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12945     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12946         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12947         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12948       } else boards[0][CASTLING][2] = NoRights;
12949     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12950         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12951         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12952       } else boards[0][CASTLING][5] = NoRights;
12953     }
12954     SendToProgram("force\n", &first);
12955     if (blackPlaysFirst) {
12956         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12957         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12958         currentMove = forwardMostMove = backwardMostMove = 1;
12959         CopyBoard(boards[1], boards[0]);
12960     } else {
12961         currentMove = forwardMostMove = backwardMostMove = 0;
12962     }
12963     SendBoard(&first, forwardMostMove);
12964     if (appData.debugMode) {
12965         fprintf(debugFP, "EditPosDone\n");
12966     }
12967     DisplayTitle("");
12968     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12969     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12970     gameMode = EditGame;
12971     ModeHighlight();
12972     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12973     ClearHighlights(); /* [AS] */
12974 }
12975
12976 /* Pause for `ms' milliseconds */
12977 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12978 void
12979 TimeDelay(ms)
12980      long ms;
12981 {
12982     TimeMark m1, m2;
12983
12984     GetTimeMark(&m1);
12985     do {
12986         GetTimeMark(&m2);
12987     } while (SubtractTimeMarks(&m2, &m1) < ms);
12988 }
12989
12990 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12991 void
12992 SendMultiLineToICS(buf)
12993      char *buf;
12994 {
12995     char temp[MSG_SIZ+1], *p;
12996     int len;
12997
12998     len = strlen(buf);
12999     if (len > MSG_SIZ)
13000       len = MSG_SIZ;
13001
13002     strncpy(temp, buf, len);
13003     temp[len] = 0;
13004
13005     p = temp;
13006     while (*p) {
13007         if (*p == '\n' || *p == '\r')
13008           *p = ' ';
13009         ++p;
13010     }
13011
13012     strcat(temp, "\n");
13013     SendToICS(temp);
13014     SendToPlayer(temp, strlen(temp));
13015 }
13016
13017 void
13018 SetWhiteToPlayEvent()
13019 {
13020     if (gameMode == EditPosition) {
13021         blackPlaysFirst = FALSE;
13022         DisplayBothClocks();    /* works because currentMove is 0 */
13023     } else if (gameMode == IcsExamining) {
13024         SendToICS(ics_prefix);
13025         SendToICS("tomove white\n");
13026     }
13027 }
13028
13029 void
13030 SetBlackToPlayEvent()
13031 {
13032     if (gameMode == EditPosition) {
13033         blackPlaysFirst = TRUE;
13034         currentMove = 1;        /* kludge */
13035         DisplayBothClocks();
13036         currentMove = 0;
13037     } else if (gameMode == IcsExamining) {
13038         SendToICS(ics_prefix);
13039         SendToICS("tomove black\n");
13040     }
13041 }
13042
13043 void
13044 EditPositionMenuEvent(selection, x, y)
13045      ChessSquare selection;
13046      int x, y;
13047 {
13048     char buf[MSG_SIZ];
13049     ChessSquare piece = boards[0][y][x];
13050
13051     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13052
13053     switch (selection) {
13054       case ClearBoard:
13055         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13056             SendToICS(ics_prefix);
13057             SendToICS("bsetup clear\n");
13058         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13059             SendToICS(ics_prefix);
13060             SendToICS("clearboard\n");
13061         } else {
13062             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13063                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13064                 for (y = 0; y < BOARD_HEIGHT; y++) {
13065                     if (gameMode == IcsExamining) {
13066                         if (boards[currentMove][y][x] != EmptySquare) {
13067                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13068                                     AAA + x, ONE + y);
13069                             SendToICS(buf);
13070                         }
13071                     } else {
13072                         boards[0][y][x] = p;
13073                     }
13074                 }
13075             }
13076         }
13077         if (gameMode == EditPosition) {
13078             DrawPosition(FALSE, boards[0]);
13079         }
13080         break;
13081
13082       case WhitePlay:
13083         SetWhiteToPlayEvent();
13084         break;
13085
13086       case BlackPlay:
13087         SetBlackToPlayEvent();
13088         break;
13089
13090       case EmptySquare:
13091         if (gameMode == IcsExamining) {
13092             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13093             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13094             SendToICS(buf);
13095         } else {
13096             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13097                 if(x == BOARD_LEFT-2) {
13098                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13099                     boards[0][y][1] = 0;
13100                 } else
13101                 if(x == BOARD_RGHT+1) {
13102                     if(y >= gameInfo.holdingsSize) break;
13103                     boards[0][y][BOARD_WIDTH-2] = 0;
13104                 } else break;
13105             }
13106             boards[0][y][x] = EmptySquare;
13107             DrawPosition(FALSE, boards[0]);
13108         }
13109         break;
13110
13111       case PromotePiece:
13112         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13113            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13114             selection = (ChessSquare) (PROMOTED piece);
13115         } else if(piece == EmptySquare) selection = WhiteSilver;
13116         else selection = (ChessSquare)((int)piece - 1);
13117         goto defaultlabel;
13118
13119       case DemotePiece:
13120         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13121            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13122             selection = (ChessSquare) (DEMOTED piece);
13123         } else if(piece == EmptySquare) selection = BlackSilver;
13124         else selection = (ChessSquare)((int)piece + 1);
13125         goto defaultlabel;
13126
13127       case WhiteQueen:
13128       case BlackQueen:
13129         if(gameInfo.variant == VariantShatranj ||
13130            gameInfo.variant == VariantXiangqi  ||
13131            gameInfo.variant == VariantCourier  ||
13132            gameInfo.variant == VariantMakruk     )
13133             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13134         goto defaultlabel;
13135
13136       case WhiteKing:
13137       case BlackKing:
13138         if(gameInfo.variant == VariantXiangqi)
13139             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13140         if(gameInfo.variant == VariantKnightmate)
13141             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13142       default:
13143         defaultlabel:
13144         if (gameMode == IcsExamining) {
13145             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13146             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13147                      PieceToChar(selection), AAA + x, ONE + y);
13148             SendToICS(buf);
13149         } else {
13150             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13151                 int n;
13152                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13153                     n = PieceToNumber(selection - BlackPawn);
13154                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13155                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13156                     boards[0][BOARD_HEIGHT-1-n][1]++;
13157                 } else
13158                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13159                     n = PieceToNumber(selection);
13160                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13161                     boards[0][n][BOARD_WIDTH-1] = selection;
13162                     boards[0][n][BOARD_WIDTH-2]++;
13163                 }
13164             } else
13165             boards[0][y][x] = selection;
13166             DrawPosition(TRUE, boards[0]);
13167         }
13168         break;
13169     }
13170 }
13171
13172
13173 void
13174 DropMenuEvent(selection, x, y)
13175      ChessSquare selection;
13176      int x, y;
13177 {
13178     ChessMove moveType;
13179
13180     switch (gameMode) {
13181       case IcsPlayingWhite:
13182       case MachinePlaysBlack:
13183         if (!WhiteOnMove(currentMove)) {
13184             DisplayMoveError(_("It is Black's turn"));
13185             return;
13186         }
13187         moveType = WhiteDrop;
13188         break;
13189       case IcsPlayingBlack:
13190       case MachinePlaysWhite:
13191         if (WhiteOnMove(currentMove)) {
13192             DisplayMoveError(_("It is White's turn"));
13193             return;
13194         }
13195         moveType = BlackDrop;
13196         break;
13197       case EditGame:
13198         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13199         break;
13200       default:
13201         return;
13202     }
13203
13204     if (moveType == BlackDrop && selection < BlackPawn) {
13205       selection = (ChessSquare) ((int) selection
13206                                  + (int) BlackPawn - (int) WhitePawn);
13207     }
13208     if (boards[currentMove][y][x] != EmptySquare) {
13209         DisplayMoveError(_("That square is occupied"));
13210         return;
13211     }
13212
13213     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13214 }
13215
13216 void
13217 AcceptEvent()
13218 {
13219     /* Accept a pending offer of any kind from opponent */
13220
13221     if (appData.icsActive) {
13222         SendToICS(ics_prefix);
13223         SendToICS("accept\n");
13224     } else if (cmailMsgLoaded) {
13225         if (currentMove == cmailOldMove &&
13226             commentList[cmailOldMove] != NULL &&
13227             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13228                    "Black offers a draw" : "White offers a draw")) {
13229             TruncateGame();
13230             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13231             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13232         } else {
13233             DisplayError(_("There is no pending offer on this move"), 0);
13234             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13235         }
13236     } else {
13237         /* Not used for offers from chess program */
13238     }
13239 }
13240
13241 void
13242 DeclineEvent()
13243 {
13244     /* Decline a pending offer of any kind from opponent */
13245
13246     if (appData.icsActive) {
13247         SendToICS(ics_prefix);
13248         SendToICS("decline\n");
13249     } else if (cmailMsgLoaded) {
13250         if (currentMove == cmailOldMove &&
13251             commentList[cmailOldMove] != NULL &&
13252             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13253                    "Black offers a draw" : "White offers a draw")) {
13254 #ifdef NOTDEF
13255             AppendComment(cmailOldMove, "Draw declined", TRUE);
13256             DisplayComment(cmailOldMove - 1, "Draw declined");
13257 #endif /*NOTDEF*/
13258         } else {
13259             DisplayError(_("There is no pending offer on this move"), 0);
13260         }
13261     } else {
13262         /* Not used for offers from chess program */
13263     }
13264 }
13265
13266 void
13267 RematchEvent()
13268 {
13269     /* Issue ICS rematch command */
13270     if (appData.icsActive) {
13271         SendToICS(ics_prefix);
13272         SendToICS("rematch\n");
13273     }
13274 }
13275
13276 void
13277 CallFlagEvent()
13278 {
13279     /* Call your opponent's flag (claim a win on time) */
13280     if (appData.icsActive) {
13281         SendToICS(ics_prefix);
13282         SendToICS("flag\n");
13283     } else {
13284         switch (gameMode) {
13285           default:
13286             return;
13287           case MachinePlaysWhite:
13288             if (whiteFlag) {
13289                 if (blackFlag)
13290                   GameEnds(GameIsDrawn, "Both players ran out of time",
13291                            GE_PLAYER);
13292                 else
13293                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13294             } else {
13295                 DisplayError(_("Your opponent is not out of time"), 0);
13296             }
13297             break;
13298           case MachinePlaysBlack:
13299             if (blackFlag) {
13300                 if (whiteFlag)
13301                   GameEnds(GameIsDrawn, "Both players ran out of time",
13302                            GE_PLAYER);
13303                 else
13304                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13305             } else {
13306                 DisplayError(_("Your opponent is not out of time"), 0);
13307             }
13308             break;
13309         }
13310     }
13311 }
13312
13313 void
13314 ClockClick(int which)
13315 {       // [HGM] code moved to back-end from winboard.c
13316         if(which) { // black clock
13317           if (gameMode == EditPosition || gameMode == IcsExamining) {
13318             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13319             SetBlackToPlayEvent();
13320           } else if (gameMode == EditGame || shiftKey) {
13321             AdjustClock(which, -1);
13322           } else if (gameMode == IcsPlayingWhite ||
13323                      gameMode == MachinePlaysBlack) {
13324             CallFlagEvent();
13325           }
13326         } else { // white clock
13327           if (gameMode == EditPosition || gameMode == IcsExamining) {
13328             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13329             SetWhiteToPlayEvent();
13330           } else if (gameMode == EditGame || shiftKey) {
13331             AdjustClock(which, -1);
13332           } else if (gameMode == IcsPlayingBlack ||
13333                    gameMode == MachinePlaysWhite) {
13334             CallFlagEvent();
13335           }
13336         }
13337 }
13338
13339 void
13340 DrawEvent()
13341 {
13342     /* Offer draw or accept pending draw offer from opponent */
13343
13344     if (appData.icsActive) {
13345         /* Note: tournament rules require draw offers to be
13346            made after you make your move but before you punch
13347            your clock.  Currently ICS doesn't let you do that;
13348            instead, you immediately punch your clock after making
13349            a move, but you can offer a draw at any time. */
13350
13351         SendToICS(ics_prefix);
13352         SendToICS("draw\n");
13353         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13354     } else if (cmailMsgLoaded) {
13355         if (currentMove == cmailOldMove &&
13356             commentList[cmailOldMove] != NULL &&
13357             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13358                    "Black offers a draw" : "White offers a draw")) {
13359             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13360             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13361         } else if (currentMove == cmailOldMove + 1) {
13362             char *offer = WhiteOnMove(cmailOldMove) ?
13363               "White offers a draw" : "Black offers a draw";
13364             AppendComment(currentMove, offer, TRUE);
13365             DisplayComment(currentMove - 1, offer);
13366             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13367         } else {
13368             DisplayError(_("You must make your move before offering a draw"), 0);
13369             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13370         }
13371     } else if (first.offeredDraw) {
13372         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13373     } else {
13374         if (first.sendDrawOffers) {
13375             SendToProgram("draw\n", &first);
13376             userOfferedDraw = TRUE;
13377         }
13378     }
13379 }
13380
13381 void
13382 AdjournEvent()
13383 {
13384     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13385
13386     if (appData.icsActive) {
13387         SendToICS(ics_prefix);
13388         SendToICS("adjourn\n");
13389     } else {
13390         /* Currently GNU Chess doesn't offer or accept Adjourns */
13391     }
13392 }
13393
13394
13395 void
13396 AbortEvent()
13397 {
13398     /* Offer Abort or accept pending Abort offer from opponent */
13399
13400     if (appData.icsActive) {
13401         SendToICS(ics_prefix);
13402         SendToICS("abort\n");
13403     } else {
13404         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13405     }
13406 }
13407
13408 void
13409 ResignEvent()
13410 {
13411     /* Resign.  You can do this even if it's not your turn. */
13412
13413     if (appData.icsActive) {
13414         SendToICS(ics_prefix);
13415         SendToICS("resign\n");
13416     } else {
13417         switch (gameMode) {
13418           case MachinePlaysWhite:
13419             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13420             break;
13421           case MachinePlaysBlack:
13422             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13423             break;
13424           case EditGame:
13425             if (cmailMsgLoaded) {
13426                 TruncateGame();
13427                 if (WhiteOnMove(cmailOldMove)) {
13428                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13429                 } else {
13430                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13431                 }
13432                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13433             }
13434             break;
13435           default:
13436             break;
13437         }
13438     }
13439 }
13440
13441
13442 void
13443 StopObservingEvent()
13444 {
13445     /* Stop observing current games */
13446     SendToICS(ics_prefix);
13447     SendToICS("unobserve\n");
13448 }
13449
13450 void
13451 StopExaminingEvent()
13452 {
13453     /* Stop observing current game */
13454     SendToICS(ics_prefix);
13455     SendToICS("unexamine\n");
13456 }
13457
13458 void
13459 ForwardInner(target)
13460      int target;
13461 {
13462     int limit;
13463
13464     if (appData.debugMode)
13465         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13466                 target, currentMove, forwardMostMove);
13467
13468     if (gameMode == EditPosition)
13469       return;
13470
13471     if (gameMode == PlayFromGameFile && !pausing)
13472       PauseEvent();
13473
13474     if (gameMode == IcsExamining && pausing)
13475       limit = pauseExamForwardMostMove;
13476     else
13477       limit = forwardMostMove;
13478
13479     if (target > limit) target = limit;
13480
13481     if (target > 0 && moveList[target - 1][0]) {
13482         int fromX, fromY, toX, toY;
13483         toX = moveList[target - 1][2] - AAA;
13484         toY = moveList[target - 1][3] - ONE;
13485         if (moveList[target - 1][1] == '@') {
13486             if (appData.highlightLastMove) {
13487                 SetHighlights(-1, -1, toX, toY);
13488             }
13489         } else {
13490             fromX = moveList[target - 1][0] - AAA;
13491             fromY = moveList[target - 1][1] - ONE;
13492             if (target == currentMove + 1) {
13493                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13494             }
13495             if (appData.highlightLastMove) {
13496                 SetHighlights(fromX, fromY, toX, toY);
13497             }
13498         }
13499     }
13500     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13501         gameMode == Training || gameMode == PlayFromGameFile ||
13502         gameMode == AnalyzeFile) {
13503         while (currentMove < target) {
13504             SendMoveToProgram(currentMove++, &first);
13505         }
13506     } else {
13507         currentMove = target;
13508     }
13509
13510     if (gameMode == EditGame || gameMode == EndOfGame) {
13511         whiteTimeRemaining = timeRemaining[0][currentMove];
13512         blackTimeRemaining = timeRemaining[1][currentMove];
13513     }
13514     DisplayBothClocks();
13515     DisplayMove(currentMove - 1);
13516     DrawPosition(FALSE, boards[currentMove]);
13517     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13518     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13519         DisplayComment(currentMove - 1, commentList[currentMove]);
13520     }
13521     DisplayBook(currentMove);
13522 }
13523
13524
13525 void
13526 ForwardEvent()
13527 {
13528     if (gameMode == IcsExamining && !pausing) {
13529         SendToICS(ics_prefix);
13530         SendToICS("forward\n");
13531     } else {
13532         ForwardInner(currentMove + 1);
13533     }
13534 }
13535
13536 void
13537 ToEndEvent()
13538 {
13539     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13540         /* to optimze, we temporarily turn off analysis mode while we feed
13541          * the remaining moves to the engine. Otherwise we get analysis output
13542          * after each move.
13543          */
13544         if (first.analysisSupport) {
13545           SendToProgram("exit\nforce\n", &first);
13546           first.analyzing = FALSE;
13547         }
13548     }
13549
13550     if (gameMode == IcsExamining && !pausing) {
13551         SendToICS(ics_prefix);
13552         SendToICS("forward 999999\n");
13553     } else {
13554         ForwardInner(forwardMostMove);
13555     }
13556
13557     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13558         /* we have fed all the moves, so reactivate analysis mode */
13559         SendToProgram("analyze\n", &first);
13560         first.analyzing = TRUE;
13561         /*first.maybeThinking = TRUE;*/
13562         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13563     }
13564 }
13565
13566 void
13567 BackwardInner(target)
13568      int target;
13569 {
13570     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13571
13572     if (appData.debugMode)
13573         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13574                 target, currentMove, forwardMostMove);
13575
13576     if (gameMode == EditPosition) return;
13577     if (currentMove <= backwardMostMove) {
13578         ClearHighlights();
13579         DrawPosition(full_redraw, boards[currentMove]);
13580         return;
13581     }
13582     if (gameMode == PlayFromGameFile && !pausing)
13583       PauseEvent();
13584
13585     if (moveList[target][0]) {
13586         int fromX, fromY, toX, toY;
13587         toX = moveList[target][2] - AAA;
13588         toY = moveList[target][3] - ONE;
13589         if (moveList[target][1] == '@') {
13590             if (appData.highlightLastMove) {
13591                 SetHighlights(-1, -1, toX, toY);
13592             }
13593         } else {
13594             fromX = moveList[target][0] - AAA;
13595             fromY = moveList[target][1] - ONE;
13596             if (target == currentMove - 1) {
13597                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13598             }
13599             if (appData.highlightLastMove) {
13600                 SetHighlights(fromX, fromY, toX, toY);
13601             }
13602         }
13603     }
13604     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13605         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13606         while (currentMove > target) {
13607             SendToProgram("undo\n", &first);
13608             currentMove--;
13609         }
13610     } else {
13611         currentMove = target;
13612     }
13613
13614     if (gameMode == EditGame || gameMode == EndOfGame) {
13615         whiteTimeRemaining = timeRemaining[0][currentMove];
13616         blackTimeRemaining = timeRemaining[1][currentMove];
13617     }
13618     DisplayBothClocks();
13619     DisplayMove(currentMove - 1);
13620     DrawPosition(full_redraw, boards[currentMove]);
13621     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13622     // [HGM] PV info: routine tests if comment empty
13623     DisplayComment(currentMove - 1, commentList[currentMove]);
13624     DisplayBook(currentMove);
13625 }
13626
13627 void
13628 BackwardEvent()
13629 {
13630     if (gameMode == IcsExamining && !pausing) {
13631         SendToICS(ics_prefix);
13632         SendToICS("backward\n");
13633     } else {
13634         BackwardInner(currentMove - 1);
13635     }
13636 }
13637
13638 void
13639 ToStartEvent()
13640 {
13641     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13642         /* to optimize, we temporarily turn off analysis mode while we undo
13643          * all the moves. Otherwise we get analysis output after each undo.
13644          */
13645         if (first.analysisSupport) {
13646           SendToProgram("exit\nforce\n", &first);
13647           first.analyzing = FALSE;
13648         }
13649     }
13650
13651     if (gameMode == IcsExamining && !pausing) {
13652         SendToICS(ics_prefix);
13653         SendToICS("backward 999999\n");
13654     } else {
13655         BackwardInner(backwardMostMove);
13656     }
13657
13658     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13659         /* we have fed all the moves, so reactivate analysis mode */
13660         SendToProgram("analyze\n", &first);
13661         first.analyzing = TRUE;
13662         /*first.maybeThinking = TRUE;*/
13663         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13664     }
13665 }
13666
13667 void
13668 ToNrEvent(int to)
13669 {
13670   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13671   if (to >= forwardMostMove) to = forwardMostMove;
13672   if (to <= backwardMostMove) to = backwardMostMove;
13673   if (to < currentMove) {
13674     BackwardInner(to);
13675   } else {
13676     ForwardInner(to);
13677   }
13678 }
13679
13680 void
13681 RevertEvent(Boolean annotate)
13682 {
13683     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13684         return;
13685     }
13686     if (gameMode != IcsExamining) {
13687         DisplayError(_("You are not examining a game"), 0);
13688         return;
13689     }
13690     if (pausing) {
13691         DisplayError(_("You can't revert while pausing"), 0);
13692         return;
13693     }
13694     SendToICS(ics_prefix);
13695     SendToICS("revert\n");
13696 }
13697
13698 void
13699 RetractMoveEvent()
13700 {
13701     switch (gameMode) {
13702       case MachinePlaysWhite:
13703       case MachinePlaysBlack:
13704         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13705             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13706             return;
13707         }
13708         if (forwardMostMove < 2) return;
13709         currentMove = forwardMostMove = forwardMostMove - 2;
13710         whiteTimeRemaining = timeRemaining[0][currentMove];
13711         blackTimeRemaining = timeRemaining[1][currentMove];
13712         DisplayBothClocks();
13713         DisplayMove(currentMove - 1);
13714         ClearHighlights();/*!! could figure this out*/
13715         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13716         SendToProgram("remove\n", &first);
13717         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13718         break;
13719
13720       case BeginningOfGame:
13721       default:
13722         break;
13723
13724       case IcsPlayingWhite:
13725       case IcsPlayingBlack:
13726         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13727             SendToICS(ics_prefix);
13728             SendToICS("takeback 2\n");
13729         } else {
13730             SendToICS(ics_prefix);
13731             SendToICS("takeback 1\n");
13732         }
13733         break;
13734     }
13735 }
13736
13737 void
13738 MoveNowEvent()
13739 {
13740     ChessProgramState *cps;
13741
13742     switch (gameMode) {
13743       case MachinePlaysWhite:
13744         if (!WhiteOnMove(forwardMostMove)) {
13745             DisplayError(_("It is your turn"), 0);
13746             return;
13747         }
13748         cps = &first;
13749         break;
13750       case MachinePlaysBlack:
13751         if (WhiteOnMove(forwardMostMove)) {
13752             DisplayError(_("It is your turn"), 0);
13753             return;
13754         }
13755         cps = &first;
13756         break;
13757       case TwoMachinesPlay:
13758         if (WhiteOnMove(forwardMostMove) ==
13759             (first.twoMachinesColor[0] == 'w')) {
13760             cps = &first;
13761         } else {
13762             cps = &second;
13763         }
13764         break;
13765       case BeginningOfGame:
13766       default:
13767         return;
13768     }
13769     SendToProgram("?\n", cps);
13770 }
13771
13772 void
13773 TruncateGameEvent()
13774 {
13775     EditGameEvent();
13776     if (gameMode != EditGame) return;
13777     TruncateGame();
13778 }
13779
13780 void
13781 TruncateGame()
13782 {
13783     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13784     if (forwardMostMove > currentMove) {
13785         if (gameInfo.resultDetails != NULL) {
13786             free(gameInfo.resultDetails);
13787             gameInfo.resultDetails = NULL;
13788             gameInfo.result = GameUnfinished;
13789         }
13790         forwardMostMove = currentMove;
13791         HistorySet(parseList, backwardMostMove, forwardMostMove,
13792                    currentMove-1);
13793     }
13794 }
13795
13796 void
13797 HintEvent()
13798 {
13799     if (appData.noChessProgram) return;
13800     switch (gameMode) {
13801       case MachinePlaysWhite:
13802         if (WhiteOnMove(forwardMostMove)) {
13803             DisplayError(_("Wait until your turn"), 0);
13804             return;
13805         }
13806         break;
13807       case BeginningOfGame:
13808       case MachinePlaysBlack:
13809         if (!WhiteOnMove(forwardMostMove)) {
13810             DisplayError(_("Wait until your turn"), 0);
13811             return;
13812         }
13813         break;
13814       default:
13815         DisplayError(_("No hint available"), 0);
13816         return;
13817     }
13818     SendToProgram("hint\n", &first);
13819     hintRequested = TRUE;
13820 }
13821
13822 void
13823 BookEvent()
13824 {
13825     if (appData.noChessProgram) return;
13826     switch (gameMode) {
13827       case MachinePlaysWhite:
13828         if (WhiteOnMove(forwardMostMove)) {
13829             DisplayError(_("Wait until your turn"), 0);
13830             return;
13831         }
13832         break;
13833       case BeginningOfGame:
13834       case MachinePlaysBlack:
13835         if (!WhiteOnMove(forwardMostMove)) {
13836             DisplayError(_("Wait until your turn"), 0);
13837             return;
13838         }
13839         break;
13840       case EditPosition:
13841         EditPositionDone(TRUE);
13842         break;
13843       case TwoMachinesPlay:
13844         return;
13845       default:
13846         break;
13847     }
13848     SendToProgram("bk\n", &first);
13849     bookOutput[0] = NULLCHAR;
13850     bookRequested = TRUE;
13851 }
13852
13853 void
13854 AboutGameEvent()
13855 {
13856     char *tags = PGNTags(&gameInfo);
13857     TagsPopUp(tags, CmailMsg());
13858     free(tags);
13859 }
13860
13861 /* end button procedures */
13862
13863 void
13864 PrintPosition(fp, move)
13865      FILE *fp;
13866      int move;
13867 {
13868     int i, j;
13869
13870     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13871         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13872             char c = PieceToChar(boards[move][i][j]);
13873             fputc(c == 'x' ? '.' : c, fp);
13874             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13875         }
13876     }
13877     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13878       fprintf(fp, "white to play\n");
13879     else
13880       fprintf(fp, "black to play\n");
13881 }
13882
13883 void
13884 PrintOpponents(fp)
13885      FILE *fp;
13886 {
13887     if (gameInfo.white != NULL) {
13888         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13889     } else {
13890         fprintf(fp, "\n");
13891     }
13892 }
13893
13894 /* Find last component of program's own name, using some heuristics */
13895 void
13896 TidyProgramName(prog, host, buf)
13897      char *prog, *host, buf[MSG_SIZ];
13898 {
13899     char *p, *q;
13900     int local = (strcmp(host, "localhost") == 0);
13901     while (!local && (p = strchr(prog, ';')) != NULL) {
13902         p++;
13903         while (*p == ' ') p++;
13904         prog = p;
13905     }
13906     if (*prog == '"' || *prog == '\'') {
13907         q = strchr(prog + 1, *prog);
13908     } else {
13909         q = strchr(prog, ' ');
13910     }
13911     if (q == NULL) q = prog + strlen(prog);
13912     p = q;
13913     while (p >= prog && *p != '/' && *p != '\\') p--;
13914     p++;
13915     if(p == prog && *p == '"') p++;
13916     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13917     memcpy(buf, p, q - p);
13918     buf[q - p] = NULLCHAR;
13919     if (!local) {
13920         strcat(buf, "@");
13921         strcat(buf, host);
13922     }
13923 }
13924
13925 char *
13926 TimeControlTagValue()
13927 {
13928     char buf[MSG_SIZ];
13929     if (!appData.clockMode) {
13930       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13931     } else if (movesPerSession > 0) {
13932       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13933     } else if (timeIncrement == 0) {
13934       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13935     } else {
13936       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13937     }
13938     return StrSave(buf);
13939 }
13940
13941 void
13942 SetGameInfo()
13943 {
13944     /* This routine is used only for certain modes */
13945     VariantClass v = gameInfo.variant;
13946     ChessMove r = GameUnfinished;
13947     char *p = NULL;
13948
13949     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13950         r = gameInfo.result;
13951         p = gameInfo.resultDetails;
13952         gameInfo.resultDetails = NULL;
13953     }
13954     ClearGameInfo(&gameInfo);
13955     gameInfo.variant = v;
13956
13957     switch (gameMode) {
13958       case MachinePlaysWhite:
13959         gameInfo.event = StrSave( appData.pgnEventHeader );
13960         gameInfo.site = StrSave(HostName());
13961         gameInfo.date = PGNDate();
13962         gameInfo.round = StrSave("-");
13963         gameInfo.white = StrSave(first.tidy);
13964         gameInfo.black = StrSave(UserName());
13965         gameInfo.timeControl = TimeControlTagValue();
13966         break;
13967
13968       case MachinePlaysBlack:
13969         gameInfo.event = StrSave( appData.pgnEventHeader );
13970         gameInfo.site = StrSave(HostName());
13971         gameInfo.date = PGNDate();
13972         gameInfo.round = StrSave("-");
13973         gameInfo.white = StrSave(UserName());
13974         gameInfo.black = StrSave(first.tidy);
13975         gameInfo.timeControl = TimeControlTagValue();
13976         break;
13977
13978       case TwoMachinesPlay:
13979         gameInfo.event = StrSave( appData.pgnEventHeader );
13980         gameInfo.site = StrSave(HostName());
13981         gameInfo.date = PGNDate();
13982         if (roundNr > 0) {
13983             char buf[MSG_SIZ];
13984             snprintf(buf, MSG_SIZ, "%d", roundNr);
13985             gameInfo.round = StrSave(buf);
13986         } else {
13987             gameInfo.round = StrSave("-");
13988         }
13989         if (first.twoMachinesColor[0] == 'w') {
13990             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13991             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13992         } else {
13993             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13994             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13995         }
13996         gameInfo.timeControl = TimeControlTagValue();
13997         break;
13998
13999       case EditGame:
14000         gameInfo.event = StrSave("Edited game");
14001         gameInfo.site = StrSave(HostName());
14002         gameInfo.date = PGNDate();
14003         gameInfo.round = StrSave("-");
14004         gameInfo.white = StrSave("-");
14005         gameInfo.black = StrSave("-");
14006         gameInfo.result = r;
14007         gameInfo.resultDetails = p;
14008         break;
14009
14010       case EditPosition:
14011         gameInfo.event = StrSave("Edited position");
14012         gameInfo.site = StrSave(HostName());
14013         gameInfo.date = PGNDate();
14014         gameInfo.round = StrSave("-");
14015         gameInfo.white = StrSave("-");
14016         gameInfo.black = StrSave("-");
14017         break;
14018
14019       case IcsPlayingWhite:
14020       case IcsPlayingBlack:
14021       case IcsObserving:
14022       case IcsExamining:
14023         break;
14024
14025       case PlayFromGameFile:
14026         gameInfo.event = StrSave("Game from non-PGN file");
14027         gameInfo.site = StrSave(HostName());
14028         gameInfo.date = PGNDate();
14029         gameInfo.round = StrSave("-");
14030         gameInfo.white = StrSave("?");
14031         gameInfo.black = StrSave("?");
14032         break;
14033
14034       default:
14035         break;
14036     }
14037 }
14038
14039 void
14040 ReplaceComment(index, text)
14041      int index;
14042      char *text;
14043 {
14044     int len;
14045     char *p;
14046     float score;
14047
14048     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14049        pvInfoList[index-1].depth == len &&
14050        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14051        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14052     while (*text == '\n') text++;
14053     len = strlen(text);
14054     while (len > 0 && text[len - 1] == '\n') len--;
14055
14056     if (commentList[index] != NULL)
14057       free(commentList[index]);
14058
14059     if (len == 0) {
14060         commentList[index] = NULL;
14061         return;
14062     }
14063   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14064       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14065       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14066     commentList[index] = (char *) malloc(len + 2);
14067     strncpy(commentList[index], text, len);
14068     commentList[index][len] = '\n';
14069     commentList[index][len + 1] = NULLCHAR;
14070   } else {
14071     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14072     char *p;
14073     commentList[index] = (char *) malloc(len + 7);
14074     safeStrCpy(commentList[index], "{\n", 3);
14075     safeStrCpy(commentList[index]+2, text, len+1);
14076     commentList[index][len+2] = NULLCHAR;
14077     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14078     strcat(commentList[index], "\n}\n");
14079   }
14080 }
14081
14082 void
14083 CrushCRs(text)
14084      char *text;
14085 {
14086   char *p = text;
14087   char *q = text;
14088   char ch;
14089
14090   do {
14091     ch = *p++;
14092     if (ch == '\r') continue;
14093     *q++ = ch;
14094   } while (ch != '\0');
14095 }
14096
14097 void
14098 AppendComment(index, text, addBraces)
14099      int index;
14100      char *text;
14101      Boolean addBraces; // [HGM] braces: tells if we should add {}
14102 {
14103     int oldlen, len;
14104     char *old;
14105
14106 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14107     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14108
14109     CrushCRs(text);
14110     while (*text == '\n') text++;
14111     len = strlen(text);
14112     while (len > 0 && text[len - 1] == '\n') len--;
14113
14114     if (len == 0) return;
14115
14116     if (commentList[index] != NULL) {
14117         old = commentList[index];
14118         oldlen = strlen(old);
14119         while(commentList[index][oldlen-1] ==  '\n')
14120           commentList[index][--oldlen] = NULLCHAR;
14121         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14122         safeStrCpy(commentList[index], old, oldlen + len + 6);
14123         free(old);
14124         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14125         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14126           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14127           while (*text == '\n') { text++; len--; }
14128           commentList[index][--oldlen] = NULLCHAR;
14129       }
14130         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14131         else          strcat(commentList[index], "\n");
14132         strcat(commentList[index], text);
14133         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14134         else          strcat(commentList[index], "\n");
14135     } else {
14136         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14137         if(addBraces)
14138           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14139         else commentList[index][0] = NULLCHAR;
14140         strcat(commentList[index], text);
14141         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14142         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14143     }
14144 }
14145
14146 static char * FindStr( char * text, char * sub_text )
14147 {
14148     char * result = strstr( text, sub_text );
14149
14150     if( result != NULL ) {
14151         result += strlen( sub_text );
14152     }
14153
14154     return result;
14155 }
14156
14157 /* [AS] Try to extract PV info from PGN comment */
14158 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14159 char *GetInfoFromComment( int index, char * text )
14160 {
14161     char * sep = text, *p;
14162
14163     if( text != NULL && index > 0 ) {
14164         int score = 0;
14165         int depth = 0;
14166         int time = -1, sec = 0, deci;
14167         char * s_eval = FindStr( text, "[%eval " );
14168         char * s_emt = FindStr( text, "[%emt " );
14169
14170         if( s_eval != NULL || s_emt != NULL ) {
14171             /* New style */
14172             char delim;
14173
14174             if( s_eval != NULL ) {
14175                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14176                     return text;
14177                 }
14178
14179                 if( delim != ']' ) {
14180                     return text;
14181                 }
14182             }
14183
14184             if( s_emt != NULL ) {
14185             }
14186                 return text;
14187         }
14188         else {
14189             /* We expect something like: [+|-]nnn.nn/dd */
14190             int score_lo = 0;
14191
14192             if(*text != '{') return text; // [HGM] braces: must be normal comment
14193
14194             sep = strchr( text, '/' );
14195             if( sep == NULL || sep < (text+4) ) {
14196                 return text;
14197             }
14198
14199             p = text;
14200             if(p[1] == '(') { // comment starts with PV
14201                p = strchr(p, ')'); // locate end of PV
14202                if(p == NULL || sep < p+5) return text;
14203                // at this point we have something like "{(.*) +0.23/6 ..."
14204                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14205                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14206                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14207             }
14208             time = -1; sec = -1; deci = -1;
14209             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14210                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14211                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14212                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14213                 return text;
14214             }
14215
14216             if( score_lo < 0 || score_lo >= 100 ) {
14217                 return text;
14218             }
14219
14220             if(sec >= 0) time = 600*time + 10*sec; else
14221             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14222
14223             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14224
14225             /* [HGM] PV time: now locate end of PV info */
14226             while( *++sep >= '0' && *sep <= '9'); // strip depth
14227             if(time >= 0)
14228             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14229             if(sec >= 0)
14230             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14231             if(deci >= 0)
14232             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14233             while(*sep == ' ') sep++;
14234         }
14235
14236         if( depth <= 0 ) {
14237             return text;
14238         }
14239
14240         if( time < 0 ) {
14241             time = -1;
14242         }
14243
14244         pvInfoList[index-1].depth = depth;
14245         pvInfoList[index-1].score = score;
14246         pvInfoList[index-1].time  = 10*time; // centi-sec
14247         if(*sep == '}') *sep = 0; else *--sep = '{';
14248         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14249     }
14250     return sep;
14251 }
14252
14253 void
14254 SendToProgram(message, cps)
14255      char *message;
14256      ChessProgramState *cps;
14257 {
14258     int count, outCount, error;
14259     char buf[MSG_SIZ];
14260
14261     if (cps->pr == NULL) return;
14262     Attention(cps);
14263
14264     if (appData.debugMode) {
14265         TimeMark now;
14266         GetTimeMark(&now);
14267         fprintf(debugFP, "%ld >%-6s: %s",
14268                 SubtractTimeMarks(&now, &programStartTime),
14269                 cps->which, message);
14270     }
14271
14272     count = strlen(message);
14273     outCount = OutputToProcess(cps->pr, message, count, &error);
14274     if (outCount < count && !exiting
14275                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14276       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14277       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14278         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14279             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14280                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14281                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14282                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14283             } else {
14284                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14285                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14286                 gameInfo.result = res;
14287             }
14288             gameInfo.resultDetails = StrSave(buf);
14289         }
14290         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14291         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14292     }
14293 }
14294
14295 void
14296 ReceiveFromProgram(isr, closure, message, count, error)
14297      InputSourceRef isr;
14298      VOIDSTAR closure;
14299      char *message;
14300      int count;
14301      int error;
14302 {
14303     char *end_str;
14304     char buf[MSG_SIZ];
14305     ChessProgramState *cps = (ChessProgramState *)closure;
14306
14307     if (isr != cps->isr) return; /* Killed intentionally */
14308     if (count <= 0) {
14309         if (count == 0) {
14310             RemoveInputSource(cps->isr);
14311             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14312                     _(cps->which), cps->program);
14313         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14314                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14315                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14316                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14317                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14318                 } else {
14319                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14320                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14321                     gameInfo.result = res;
14322                 }
14323                 gameInfo.resultDetails = StrSave(buf);
14324             }
14325             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14326             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14327         } else {
14328             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14329                     _(cps->which), cps->program);
14330             RemoveInputSource(cps->isr);
14331
14332             /* [AS] Program is misbehaving badly... kill it */
14333             if( count == -2 ) {
14334                 DestroyChildProcess( cps->pr, 9 );
14335                 cps->pr = NoProc;
14336             }
14337
14338             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14339         }
14340         return;
14341     }
14342
14343     if ((end_str = strchr(message, '\r')) != NULL)
14344       *end_str = NULLCHAR;
14345     if ((end_str = strchr(message, '\n')) != NULL)
14346       *end_str = NULLCHAR;
14347
14348     if (appData.debugMode) {
14349         TimeMark now; int print = 1;
14350         char *quote = ""; char c; int i;
14351
14352         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14353                 char start = message[0];
14354                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14355                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14356                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14357                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14358                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14359                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14360                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14361                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14362                    sscanf(message, "hint: %c", &c)!=1 && 
14363                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14364                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14365                     print = (appData.engineComments >= 2);
14366                 }
14367                 message[0] = start; // restore original message
14368         }
14369         if(print) {
14370                 GetTimeMark(&now);
14371                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14372                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14373                         quote,
14374                         message);
14375         }
14376     }
14377
14378     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14379     if (appData.icsEngineAnalyze) {
14380         if (strstr(message, "whisper") != NULL ||
14381              strstr(message, "kibitz") != NULL ||
14382             strstr(message, "tellics") != NULL) return;
14383     }
14384
14385     HandleMachineMove(message, cps);
14386 }
14387
14388
14389 void
14390 SendTimeControl(cps, mps, tc, inc, sd, st)
14391      ChessProgramState *cps;
14392      int mps, inc, sd, st;
14393      long tc;
14394 {
14395     char buf[MSG_SIZ];
14396     int seconds;
14397
14398     if( timeControl_2 > 0 ) {
14399         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14400             tc = timeControl_2;
14401         }
14402     }
14403     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14404     inc /= cps->timeOdds;
14405     st  /= cps->timeOdds;
14406
14407     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14408
14409     if (st > 0) {
14410       /* Set exact time per move, normally using st command */
14411       if (cps->stKludge) {
14412         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14413         seconds = st % 60;
14414         if (seconds == 0) {
14415           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14416         } else {
14417           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14418         }
14419       } else {
14420         snprintf(buf, MSG_SIZ, "st %d\n", st);
14421       }
14422     } else {
14423       /* Set conventional or incremental time control, using level command */
14424       if (seconds == 0) {
14425         /* Note old gnuchess bug -- minutes:seconds used to not work.
14426            Fixed in later versions, but still avoid :seconds
14427            when seconds is 0. */
14428         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14429       } else {
14430         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14431                  seconds, inc/1000.);
14432       }
14433     }
14434     SendToProgram(buf, cps);
14435
14436     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14437     /* Orthogonally, limit search to given depth */
14438     if (sd > 0) {
14439       if (cps->sdKludge) {
14440         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14441       } else {
14442         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14443       }
14444       SendToProgram(buf, cps);
14445     }
14446
14447     if(cps->nps >= 0) { /* [HGM] nps */
14448         if(cps->supportsNPS == FALSE)
14449           cps->nps = -1; // don't use if engine explicitly says not supported!
14450         else {
14451           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14452           SendToProgram(buf, cps);
14453         }
14454     }
14455 }
14456
14457 ChessProgramState *WhitePlayer()
14458 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14459 {
14460     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14461        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14462         return &second;
14463     return &first;
14464 }
14465
14466 void
14467 SendTimeRemaining(cps, machineWhite)
14468      ChessProgramState *cps;
14469      int /*boolean*/ machineWhite;
14470 {
14471     char message[MSG_SIZ];
14472     long time, otime;
14473
14474     /* Note: this routine must be called when the clocks are stopped
14475        or when they have *just* been set or switched; otherwise
14476        it will be off by the time since the current tick started.
14477     */
14478     if (machineWhite) {
14479         time = whiteTimeRemaining / 10;
14480         otime = blackTimeRemaining / 10;
14481     } else {
14482         time = blackTimeRemaining / 10;
14483         otime = whiteTimeRemaining / 10;
14484     }
14485     /* [HGM] translate opponent's time by time-odds factor */
14486     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14487     if (appData.debugMode) {
14488         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14489     }
14490
14491     if (time <= 0) time = 1;
14492     if (otime <= 0) otime = 1;
14493
14494     snprintf(message, MSG_SIZ, "time %ld\n", time);
14495     SendToProgram(message, cps);
14496
14497     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14498     SendToProgram(message, cps);
14499 }
14500
14501 int
14502 BoolFeature(p, name, loc, cps)
14503      char **p;
14504      char *name;
14505      int *loc;
14506      ChessProgramState *cps;
14507 {
14508   char buf[MSG_SIZ];
14509   int len = strlen(name);
14510   int val;
14511
14512   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14513     (*p) += len + 1;
14514     sscanf(*p, "%d", &val);
14515     *loc = (val != 0);
14516     while (**p && **p != ' ')
14517       (*p)++;
14518     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14519     SendToProgram(buf, cps);
14520     return TRUE;
14521   }
14522   return FALSE;
14523 }
14524
14525 int
14526 IntFeature(p, name, loc, cps)
14527      char **p;
14528      char *name;
14529      int *loc;
14530      ChessProgramState *cps;
14531 {
14532   char buf[MSG_SIZ];
14533   int len = strlen(name);
14534   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14535     (*p) += len + 1;
14536     sscanf(*p, "%d", loc);
14537     while (**p && **p != ' ') (*p)++;
14538     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14539     SendToProgram(buf, cps);
14540     return TRUE;
14541   }
14542   return FALSE;
14543 }
14544
14545 int
14546 StringFeature(p, name, loc, cps)
14547      char **p;
14548      char *name;
14549      char loc[];
14550      ChessProgramState *cps;
14551 {
14552   char buf[MSG_SIZ];
14553   int len = strlen(name);
14554   if (strncmp((*p), name, len) == 0
14555       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14556     (*p) += len + 2;
14557     sscanf(*p, "%[^\"]", loc);
14558     while (**p && **p != '\"') (*p)++;
14559     if (**p == '\"') (*p)++;
14560     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14561     SendToProgram(buf, cps);
14562     return TRUE;
14563   }
14564   return FALSE;
14565 }
14566
14567 int
14568 ParseOption(Option *opt, ChessProgramState *cps)
14569 // [HGM] options: process the string that defines an engine option, and determine
14570 // name, type, default value, and allowed value range
14571 {
14572         char *p, *q, buf[MSG_SIZ];
14573         int n, min = (-1)<<31, max = 1<<31, def;
14574
14575         if(p = strstr(opt->name, " -spin ")) {
14576             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14577             if(max < min) max = min; // enforce consistency
14578             if(def < min) def = min;
14579             if(def > max) def = max;
14580             opt->value = def;
14581             opt->min = min;
14582             opt->max = max;
14583             opt->type = Spin;
14584         } else if((p = strstr(opt->name, " -slider "))) {
14585             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14586             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14587             if(max < min) max = min; // enforce consistency
14588             if(def < min) def = min;
14589             if(def > max) def = max;
14590             opt->value = def;
14591             opt->min = min;
14592             opt->max = max;
14593             opt->type = Spin; // Slider;
14594         } else if((p = strstr(opt->name, " -string "))) {
14595             opt->textValue = p+9;
14596             opt->type = TextBox;
14597         } else if((p = strstr(opt->name, " -file "))) {
14598             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14599             opt->textValue = p+7;
14600             opt->type = FileName; // FileName;
14601         } else if((p = strstr(opt->name, " -path "))) {
14602             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14603             opt->textValue = p+7;
14604             opt->type = PathName; // PathName;
14605         } else if(p = strstr(opt->name, " -check ")) {
14606             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14607             opt->value = (def != 0);
14608             opt->type = CheckBox;
14609         } else if(p = strstr(opt->name, " -combo ")) {
14610             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14611             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14612             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14613             opt->value = n = 0;
14614             while(q = StrStr(q, " /// ")) {
14615                 n++; *q = 0;    // count choices, and null-terminate each of them
14616                 q += 5;
14617                 if(*q == '*') { // remember default, which is marked with * prefix
14618                     q++;
14619                     opt->value = n;
14620                 }
14621                 cps->comboList[cps->comboCnt++] = q;
14622             }
14623             cps->comboList[cps->comboCnt++] = NULL;
14624             opt->max = n + 1;
14625             opt->type = ComboBox;
14626         } else if(p = strstr(opt->name, " -button")) {
14627             opt->type = Button;
14628         } else if(p = strstr(opt->name, " -save")) {
14629             opt->type = SaveButton;
14630         } else return FALSE;
14631         *p = 0; // terminate option name
14632         // now look if the command-line options define a setting for this engine option.
14633         if(cps->optionSettings && cps->optionSettings[0])
14634             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14635         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14636           snprintf(buf, MSG_SIZ, "option %s", p);
14637                 if(p = strstr(buf, ",")) *p = 0;
14638                 if(q = strchr(buf, '=')) switch(opt->type) {
14639                     case ComboBox:
14640                         for(n=0; n<opt->max; n++)
14641                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14642                         break;
14643                     case TextBox:
14644                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14645                         break;
14646                     case Spin:
14647                     case CheckBox:
14648                         opt->value = atoi(q+1);
14649                     default:
14650                         break;
14651                 }
14652                 strcat(buf, "\n");
14653                 SendToProgram(buf, cps);
14654         }
14655         return TRUE;
14656 }
14657
14658 void
14659 FeatureDone(cps, val)
14660      ChessProgramState* cps;
14661      int val;
14662 {
14663   DelayedEventCallback cb = GetDelayedEvent();
14664   if ((cb == InitBackEnd3 && cps == &first) ||
14665       (cb == SettingsMenuIfReady && cps == &second) ||
14666       (cb == LoadEngine) ||
14667       (cb == TwoMachinesEventIfReady)) {
14668     CancelDelayedEvent();
14669     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14670   }
14671   cps->initDone = val;
14672 }
14673
14674 /* Parse feature command from engine */
14675 void
14676 ParseFeatures(args, cps)
14677      char* args;
14678      ChessProgramState *cps;
14679 {
14680   char *p = args;
14681   char *q;
14682   int val;
14683   char buf[MSG_SIZ];
14684
14685   for (;;) {
14686     while (*p == ' ') p++;
14687     if (*p == NULLCHAR) return;
14688
14689     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14690     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14691     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14692     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14693     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14694     if (BoolFeature(&p, "reuse", &val, cps)) {
14695       /* Engine can disable reuse, but can't enable it if user said no */
14696       if (!val) cps->reuse = FALSE;
14697       continue;
14698     }
14699     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14700     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14701       if (gameMode == TwoMachinesPlay) {
14702         DisplayTwoMachinesTitle();
14703       } else {
14704         DisplayTitle("");
14705       }
14706       continue;
14707     }
14708     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14709     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14710     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14711     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14712     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14713     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14714     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14715     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14716     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14717     if (IntFeature(&p, "done", &val, cps)) {
14718       FeatureDone(cps, val);
14719       continue;
14720     }
14721     /* Added by Tord: */
14722     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14723     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14724     /* End of additions by Tord */
14725
14726     /* [HGM] added features: */
14727     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14728     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14729     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14730     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14731     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14732     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14733     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14734         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14735           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14736             SendToProgram(buf, cps);
14737             continue;
14738         }
14739         if(cps->nrOptions >= MAX_OPTIONS) {
14740             cps->nrOptions--;
14741             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14742             DisplayError(buf, 0);
14743         }
14744         continue;
14745     }
14746     /* End of additions by HGM */
14747
14748     /* unknown feature: complain and skip */
14749     q = p;
14750     while (*q && *q != '=') q++;
14751     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14752     SendToProgram(buf, cps);
14753     p = q;
14754     if (*p == '=') {
14755       p++;
14756       if (*p == '\"') {
14757         p++;
14758         while (*p && *p != '\"') p++;
14759         if (*p == '\"') p++;
14760       } else {
14761         while (*p && *p != ' ') p++;
14762       }
14763     }
14764   }
14765
14766 }
14767
14768 void
14769 PeriodicUpdatesEvent(newState)
14770      int newState;
14771 {
14772     if (newState == appData.periodicUpdates)
14773       return;
14774
14775     appData.periodicUpdates=newState;
14776
14777     /* Display type changes, so update it now */
14778 //    DisplayAnalysis();
14779
14780     /* Get the ball rolling again... */
14781     if (newState) {
14782         AnalysisPeriodicEvent(1);
14783         StartAnalysisClock();
14784     }
14785 }
14786
14787 void
14788 PonderNextMoveEvent(newState)
14789      int newState;
14790 {
14791     if (newState == appData.ponderNextMove) return;
14792     if (gameMode == EditPosition) EditPositionDone(TRUE);
14793     if (newState) {
14794         SendToProgram("hard\n", &first);
14795         if (gameMode == TwoMachinesPlay) {
14796             SendToProgram("hard\n", &second);
14797         }
14798     } else {
14799         SendToProgram("easy\n", &first);
14800         thinkOutput[0] = NULLCHAR;
14801         if (gameMode == TwoMachinesPlay) {
14802             SendToProgram("easy\n", &second);
14803         }
14804     }
14805     appData.ponderNextMove = newState;
14806 }
14807
14808 void
14809 NewSettingEvent(option, feature, command, value)
14810      char *command;
14811      int option, value, *feature;
14812 {
14813     char buf[MSG_SIZ];
14814
14815     if (gameMode == EditPosition) EditPositionDone(TRUE);
14816     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14817     if(feature == NULL || *feature) SendToProgram(buf, &first);
14818     if (gameMode == TwoMachinesPlay) {
14819         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14820     }
14821 }
14822
14823 void
14824 ShowThinkingEvent()
14825 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14826 {
14827     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14828     int newState = appData.showThinking
14829         // [HGM] thinking: other features now need thinking output as well
14830         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14831
14832     if (oldState == newState) return;
14833     oldState = newState;
14834     if (gameMode == EditPosition) EditPositionDone(TRUE);
14835     if (oldState) {
14836         SendToProgram("post\n", &first);
14837         if (gameMode == TwoMachinesPlay) {
14838             SendToProgram("post\n", &second);
14839         }
14840     } else {
14841         SendToProgram("nopost\n", &first);
14842         thinkOutput[0] = NULLCHAR;
14843         if (gameMode == TwoMachinesPlay) {
14844             SendToProgram("nopost\n", &second);
14845         }
14846     }
14847 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14848 }
14849
14850 void
14851 AskQuestionEvent(title, question, replyPrefix, which)
14852      char *title; char *question; char *replyPrefix; char *which;
14853 {
14854   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14855   if (pr == NoProc) return;
14856   AskQuestion(title, question, replyPrefix, pr);
14857 }
14858
14859 void
14860 TypeInEvent(char firstChar)
14861 {
14862     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14863         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14864         gameMode == AnalyzeMode || gameMode == EditGame || \r
14865         gameMode == EditPosition || gameMode == IcsExamining ||\r
14866         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14867         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14868                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14869                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14870         gameMode == Training) PopUpMoveDialog(firstChar);
14871 }
14872
14873 void
14874 TypeInDoneEvent(char *move)
14875 {
14876         Board board;
14877         int n, fromX, fromY, toX, toY;
14878         char promoChar;
14879         ChessMove moveType;\r
14880
14881         // [HGM] FENedit\r
14882         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14883                 EditPositionPasteFEN(move);\r
14884                 return;\r
14885         }\r
14886         // [HGM] movenum: allow move number to be typed in any mode\r
14887         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14888           ToNrEvent(2*n-1);\r
14889           return;\r
14890         }\r
14891
14892       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14893         gameMode != Training) {\r
14894         DisplayMoveError(_("Displayed move is not current"));\r
14895       } else {\r
14896         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14897           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14898         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14899         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14900           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14901           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14902         } else {\r
14903           DisplayMoveError(_("Could not parse move"));\r
14904         }
14905       }\r
14906 }\r
14907
14908 void
14909 DisplayMove(moveNumber)
14910      int moveNumber;
14911 {
14912     char message[MSG_SIZ];
14913     char res[MSG_SIZ];
14914     char cpThinkOutput[MSG_SIZ];
14915
14916     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14917
14918     if (moveNumber == forwardMostMove - 1 ||
14919         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14920
14921         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14922
14923         if (strchr(cpThinkOutput, '\n')) {
14924             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14925         }
14926     } else {
14927         *cpThinkOutput = NULLCHAR;
14928     }
14929
14930     /* [AS] Hide thinking from human user */
14931     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14932         *cpThinkOutput = NULLCHAR;
14933         if( thinkOutput[0] != NULLCHAR ) {
14934             int i;
14935
14936             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14937                 cpThinkOutput[i] = '.';
14938             }
14939             cpThinkOutput[i] = NULLCHAR;
14940             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14941         }
14942     }
14943
14944     if (moveNumber == forwardMostMove - 1 &&
14945         gameInfo.resultDetails != NULL) {
14946         if (gameInfo.resultDetails[0] == NULLCHAR) {
14947           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14948         } else {
14949           snprintf(res, MSG_SIZ, " {%s} %s",
14950                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14951         }
14952     } else {
14953         res[0] = NULLCHAR;
14954     }
14955
14956     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14957         DisplayMessage(res, cpThinkOutput);
14958     } else {
14959       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14960                 WhiteOnMove(moveNumber) ? " " : ".. ",
14961                 parseList[moveNumber], res);
14962         DisplayMessage(message, cpThinkOutput);
14963     }
14964 }
14965
14966 void
14967 DisplayComment(moveNumber, text)
14968      int moveNumber;
14969      char *text;
14970 {
14971     char title[MSG_SIZ];
14972     char buf[8000]; // comment can be long!
14973     int score, depth;
14974
14975     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14976       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14977     } else {
14978       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14979               WhiteOnMove(moveNumber) ? " " : ".. ",
14980               parseList[moveNumber]);
14981     }
14982     // [HGM] PV info: display PV info together with (or as) comment
14983     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14984       if(text == NULL) text = "";
14985       score = pvInfoList[moveNumber].score;
14986       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14987               depth, (pvInfoList[moveNumber].time+50)/100, text);
14988       text = buf;
14989     }
14990     if (text != NULL && (appData.autoDisplayComment || commentUp))
14991         CommentPopUp(title, text);
14992 }
14993
14994 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14995  * might be busy thinking or pondering.  It can be omitted if your
14996  * gnuchess is configured to stop thinking immediately on any user
14997  * input.  However, that gnuchess feature depends on the FIONREAD
14998  * ioctl, which does not work properly on some flavors of Unix.
14999  */
15000 void
15001 Attention(cps)
15002      ChessProgramState *cps;
15003 {
15004 #if ATTENTION
15005     if (!cps->useSigint) return;
15006     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15007     switch (gameMode) {
15008       case MachinePlaysWhite:
15009       case MachinePlaysBlack:
15010       case TwoMachinesPlay:
15011       case IcsPlayingWhite:
15012       case IcsPlayingBlack:
15013       case AnalyzeMode:
15014       case AnalyzeFile:
15015         /* Skip if we know it isn't thinking */
15016         if (!cps->maybeThinking) return;
15017         if (appData.debugMode)
15018           fprintf(debugFP, "Interrupting %s\n", cps->which);
15019         InterruptChildProcess(cps->pr);
15020         cps->maybeThinking = FALSE;
15021         break;
15022       default:
15023         break;
15024     }
15025 #endif /*ATTENTION*/
15026 }
15027
15028 int
15029 CheckFlags()
15030 {
15031     if (whiteTimeRemaining <= 0) {
15032         if (!whiteFlag) {
15033             whiteFlag = TRUE;
15034             if (appData.icsActive) {
15035                 if (appData.autoCallFlag &&
15036                     gameMode == IcsPlayingBlack && !blackFlag) {
15037                   SendToICS(ics_prefix);
15038                   SendToICS("flag\n");
15039                 }
15040             } else {
15041                 if (blackFlag) {
15042                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15043                 } else {
15044                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15045                     if (appData.autoCallFlag) {
15046                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15047                         return TRUE;
15048                     }
15049                 }
15050             }
15051         }
15052     }
15053     if (blackTimeRemaining <= 0) {
15054         if (!blackFlag) {
15055             blackFlag = TRUE;
15056             if (appData.icsActive) {
15057                 if (appData.autoCallFlag &&
15058                     gameMode == IcsPlayingWhite && !whiteFlag) {
15059                   SendToICS(ics_prefix);
15060                   SendToICS("flag\n");
15061                 }
15062             } else {
15063                 if (whiteFlag) {
15064                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15065                 } else {
15066                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15067                     if (appData.autoCallFlag) {
15068                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15069                         return TRUE;
15070                     }
15071                 }
15072             }
15073         }
15074     }
15075     return FALSE;
15076 }
15077
15078 void
15079 CheckTimeControl()
15080 {
15081     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15082         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15083
15084     /*
15085      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15086      */
15087     if ( !WhiteOnMove(forwardMostMove) ) {
15088         /* White made time control */
15089         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15090         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15091         /* [HGM] time odds: correct new time quota for time odds! */
15092                                             / WhitePlayer()->timeOdds;
15093         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15094     } else {
15095         lastBlack -= blackTimeRemaining;
15096         /* Black made time control */
15097         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15098                                             / WhitePlayer()->other->timeOdds;
15099         lastWhite = whiteTimeRemaining;
15100     }
15101 }
15102
15103 void
15104 DisplayBothClocks()
15105 {
15106     int wom = gameMode == EditPosition ?
15107       !blackPlaysFirst : WhiteOnMove(currentMove);
15108     DisplayWhiteClock(whiteTimeRemaining, wom);
15109     DisplayBlackClock(blackTimeRemaining, !wom);
15110 }
15111
15112
15113 /* Timekeeping seems to be a portability nightmare.  I think everyone
15114    has ftime(), but I'm really not sure, so I'm including some ifdefs
15115    to use other calls if you don't.  Clocks will be less accurate if
15116    you have neither ftime nor gettimeofday.
15117 */
15118
15119 /* VS 2008 requires the #include outside of the function */
15120 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15121 #include <sys/timeb.h>
15122 #endif
15123
15124 /* Get the current time as a TimeMark */
15125 void
15126 GetTimeMark(tm)
15127      TimeMark *tm;
15128 {
15129 #if HAVE_GETTIMEOFDAY
15130
15131     struct timeval timeVal;
15132     struct timezone timeZone;
15133
15134     gettimeofday(&timeVal, &timeZone);
15135     tm->sec = (long) timeVal.tv_sec;
15136     tm->ms = (int) (timeVal.tv_usec / 1000L);
15137
15138 #else /*!HAVE_GETTIMEOFDAY*/
15139 #if HAVE_FTIME
15140
15141 // include <sys/timeb.h> / moved to just above start of function
15142     struct timeb timeB;
15143
15144     ftime(&timeB);
15145     tm->sec = (long) timeB.time;
15146     tm->ms = (int) timeB.millitm;
15147
15148 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15149     tm->sec = (long) time(NULL);
15150     tm->ms = 0;
15151 #endif
15152 #endif
15153 }
15154
15155 /* Return the difference in milliseconds between two
15156    time marks.  We assume the difference will fit in a long!
15157 */
15158 long
15159 SubtractTimeMarks(tm2, tm1)
15160      TimeMark *tm2, *tm1;
15161 {
15162     return 1000L*(tm2->sec - tm1->sec) +
15163            (long) (tm2->ms - tm1->ms);
15164 }
15165
15166
15167 /*
15168  * Code to manage the game clocks.
15169  *
15170  * In tournament play, black starts the clock and then white makes a move.
15171  * We give the human user a slight advantage if he is playing white---the
15172  * clocks don't run until he makes his first move, so it takes zero time.
15173  * Also, we don't account for network lag, so we could get out of sync
15174  * with GNU Chess's clock -- but then, referees are always right.
15175  */
15176
15177 static TimeMark tickStartTM;
15178 static long intendedTickLength;
15179
15180 long
15181 NextTickLength(timeRemaining)
15182      long timeRemaining;
15183 {
15184     long nominalTickLength, nextTickLength;
15185
15186     if (timeRemaining > 0L && timeRemaining <= 10000L)
15187       nominalTickLength = 100L;
15188     else
15189       nominalTickLength = 1000L;
15190     nextTickLength = timeRemaining % nominalTickLength;
15191     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15192
15193     return nextTickLength;
15194 }
15195
15196 /* Adjust clock one minute up or down */
15197 void
15198 AdjustClock(Boolean which, int dir)
15199 {
15200     if(which) blackTimeRemaining += 60000*dir;
15201     else      whiteTimeRemaining += 60000*dir;
15202     DisplayBothClocks();
15203 }
15204
15205 /* Stop clocks and reset to a fresh time control */
15206 void
15207 ResetClocks()
15208 {
15209     (void) StopClockTimer();
15210     if (appData.icsActive) {
15211         whiteTimeRemaining = blackTimeRemaining = 0;
15212     } else if (searchTime) {
15213         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15214         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15215     } else { /* [HGM] correct new time quote for time odds */
15216         whiteTC = blackTC = fullTimeControlString;
15217         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15218         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15219     }
15220     if (whiteFlag || blackFlag) {
15221         DisplayTitle("");
15222         whiteFlag = blackFlag = FALSE;
15223     }
15224     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15225     DisplayBothClocks();
15226 }
15227
15228 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15229
15230 /* Decrement running clock by amount of time that has passed */
15231 void
15232 DecrementClocks()
15233 {
15234     long timeRemaining;
15235     long lastTickLength, fudge;
15236     TimeMark now;
15237
15238     if (!appData.clockMode) return;
15239     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15240
15241     GetTimeMark(&now);
15242
15243     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15244
15245     /* Fudge if we woke up a little too soon */
15246     fudge = intendedTickLength - lastTickLength;
15247     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15248
15249     if (WhiteOnMove(forwardMostMove)) {
15250         if(whiteNPS >= 0) lastTickLength = 0;
15251         timeRemaining = whiteTimeRemaining -= lastTickLength;
15252         if(timeRemaining < 0 && !appData.icsActive) {
15253             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15254             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15255                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15256                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15257             }
15258         }
15259         DisplayWhiteClock(whiteTimeRemaining - fudge,
15260                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15261     } else {
15262         if(blackNPS >= 0) lastTickLength = 0;
15263         timeRemaining = blackTimeRemaining -= lastTickLength;
15264         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15265             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15266             if(suddenDeath) {
15267                 blackStartMove = forwardMostMove;
15268                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15269             }
15270         }
15271         DisplayBlackClock(blackTimeRemaining - fudge,
15272                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15273     }
15274     if (CheckFlags()) return;
15275
15276     tickStartTM = now;
15277     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15278     StartClockTimer(intendedTickLength);
15279
15280     /* if the time remaining has fallen below the alarm threshold, sound the
15281      * alarm. if the alarm has sounded and (due to a takeback or time control
15282      * with increment) the time remaining has increased to a level above the
15283      * threshold, reset the alarm so it can sound again.
15284      */
15285
15286     if (appData.icsActive && appData.icsAlarm) {
15287
15288         /* make sure we are dealing with the user's clock */
15289         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15290                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15291            )) return;
15292
15293         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15294             alarmSounded = FALSE;
15295         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15296             PlayAlarmSound();
15297             alarmSounded = TRUE;
15298         }
15299     }
15300 }
15301
15302
15303 /* A player has just moved, so stop the previously running
15304    clock and (if in clock mode) start the other one.
15305    We redisplay both clocks in case we're in ICS mode, because
15306    ICS gives us an update to both clocks after every move.
15307    Note that this routine is called *after* forwardMostMove
15308    is updated, so the last fractional tick must be subtracted
15309    from the color that is *not* on move now.
15310 */
15311 void
15312 SwitchClocks(int newMoveNr)
15313 {
15314     long lastTickLength;
15315     TimeMark now;
15316     int flagged = FALSE;
15317
15318     GetTimeMark(&now);
15319
15320     if (StopClockTimer() && appData.clockMode) {
15321         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15322         if (!WhiteOnMove(forwardMostMove)) {
15323             if(blackNPS >= 0) lastTickLength = 0;
15324             blackTimeRemaining -= lastTickLength;
15325            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15326 //         if(pvInfoList[forwardMostMove].time == -1)
15327                  pvInfoList[forwardMostMove].time =               // use GUI time
15328                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15329         } else {
15330            if(whiteNPS >= 0) lastTickLength = 0;
15331            whiteTimeRemaining -= lastTickLength;
15332            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15333 //         if(pvInfoList[forwardMostMove].time == -1)
15334                  pvInfoList[forwardMostMove].time =
15335                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15336         }
15337         flagged = CheckFlags();
15338     }
15339     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15340     CheckTimeControl();
15341
15342     if (flagged || !appData.clockMode) return;
15343
15344     switch (gameMode) {
15345       case MachinePlaysBlack:
15346       case MachinePlaysWhite:
15347       case BeginningOfGame:
15348         if (pausing) return;
15349         break;
15350
15351       case EditGame:
15352       case PlayFromGameFile:
15353       case IcsExamining:
15354         return;
15355
15356       default:
15357         break;
15358     }
15359
15360     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15361         if(WhiteOnMove(forwardMostMove))
15362              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15363         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15364     }
15365
15366     tickStartTM = now;
15367     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15368       whiteTimeRemaining : blackTimeRemaining);
15369     StartClockTimer(intendedTickLength);
15370 }
15371
15372
15373 /* Stop both clocks */
15374 void
15375 StopClocks()
15376 {
15377     long lastTickLength;
15378     TimeMark now;
15379
15380     if (!StopClockTimer()) return;
15381     if (!appData.clockMode) return;
15382
15383     GetTimeMark(&now);
15384
15385     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15386     if (WhiteOnMove(forwardMostMove)) {
15387         if(whiteNPS >= 0) lastTickLength = 0;
15388         whiteTimeRemaining -= lastTickLength;
15389         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15390     } else {
15391         if(blackNPS >= 0) lastTickLength = 0;
15392         blackTimeRemaining -= lastTickLength;
15393         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15394     }
15395     CheckFlags();
15396 }
15397
15398 /* Start clock of player on move.  Time may have been reset, so
15399    if clock is already running, stop and restart it. */
15400 void
15401 StartClocks()
15402 {
15403     (void) StopClockTimer(); /* in case it was running already */
15404     DisplayBothClocks();
15405     if (CheckFlags()) return;
15406
15407     if (!appData.clockMode) return;
15408     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15409
15410     GetTimeMark(&tickStartTM);
15411     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15412       whiteTimeRemaining : blackTimeRemaining);
15413
15414    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15415     whiteNPS = blackNPS = -1;
15416     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15417        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15418         whiteNPS = first.nps;
15419     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15420        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15421         blackNPS = first.nps;
15422     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15423         whiteNPS = second.nps;
15424     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15425         blackNPS = second.nps;
15426     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15427
15428     StartClockTimer(intendedTickLength);
15429 }
15430
15431 char *
15432 TimeString(ms)
15433      long ms;
15434 {
15435     long second, minute, hour, day;
15436     char *sign = "";
15437     static char buf[32];
15438
15439     if (ms > 0 && ms <= 9900) {
15440       /* convert milliseconds to tenths, rounding up */
15441       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15442
15443       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15444       return buf;
15445     }
15446
15447     /* convert milliseconds to seconds, rounding up */
15448     /* use floating point to avoid strangeness of integer division
15449        with negative dividends on many machines */
15450     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15451
15452     if (second < 0) {
15453         sign = "-";
15454         second = -second;
15455     }
15456
15457     day = second / (60 * 60 * 24);
15458     second = second % (60 * 60 * 24);
15459     hour = second / (60 * 60);
15460     second = second % (60 * 60);
15461     minute = second / 60;
15462     second = second % 60;
15463
15464     if (day > 0)
15465       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15466               sign, day, hour, minute, second);
15467     else if (hour > 0)
15468       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15469     else
15470       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15471
15472     return buf;
15473 }
15474
15475
15476 /*
15477  * This is necessary because some C libraries aren't ANSI C compliant yet.
15478  */
15479 char *
15480 StrStr(string, match)
15481      char *string, *match;
15482 {
15483     int i, length;
15484
15485     length = strlen(match);
15486
15487     for (i = strlen(string) - length; i >= 0; i--, string++)
15488       if (!strncmp(match, string, length))
15489         return string;
15490
15491     return NULL;
15492 }
15493
15494 char *
15495 StrCaseStr(string, match)
15496      char *string, *match;
15497 {
15498     int i, j, length;
15499
15500     length = strlen(match);
15501
15502     for (i = strlen(string) - length; i >= 0; i--, string++) {
15503         for (j = 0; j < length; j++) {
15504             if (ToLower(match[j]) != ToLower(string[j]))
15505               break;
15506         }
15507         if (j == length) return string;
15508     }
15509
15510     return NULL;
15511 }
15512
15513 #ifndef _amigados
15514 int
15515 StrCaseCmp(s1, s2)
15516      char *s1, *s2;
15517 {
15518     char c1, c2;
15519
15520     for (;;) {
15521         c1 = ToLower(*s1++);
15522         c2 = ToLower(*s2++);
15523         if (c1 > c2) return 1;
15524         if (c1 < c2) return -1;
15525         if (c1 == NULLCHAR) return 0;
15526     }
15527 }
15528
15529
15530 int
15531 ToLower(c)
15532      int c;
15533 {
15534     return isupper(c) ? tolower(c) : c;
15535 }
15536
15537
15538 int
15539 ToUpper(c)
15540      int c;
15541 {
15542     return islower(c) ? toupper(c) : c;
15543 }
15544 #endif /* !_amigados    */
15545
15546 char *
15547 StrSave(s)
15548      char *s;
15549 {
15550   char *ret;
15551
15552   if ((ret = (char *) malloc(strlen(s) + 1)))
15553     {
15554       safeStrCpy(ret, s, strlen(s)+1);
15555     }
15556   return ret;
15557 }
15558
15559 char *
15560 StrSavePtr(s, savePtr)
15561      char *s, **savePtr;
15562 {
15563     if (*savePtr) {
15564         free(*savePtr);
15565     }
15566     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15567       safeStrCpy(*savePtr, s, strlen(s)+1);
15568     }
15569     return(*savePtr);
15570 }
15571
15572 char *
15573 PGNDate()
15574 {
15575     time_t clock;
15576     struct tm *tm;
15577     char buf[MSG_SIZ];
15578
15579     clock = time((time_t *)NULL);
15580     tm = localtime(&clock);
15581     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15582             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15583     return StrSave(buf);
15584 }
15585
15586
15587 char *
15588 PositionToFEN(move, overrideCastling)
15589      int move;
15590      char *overrideCastling;
15591 {
15592     int i, j, fromX, fromY, toX, toY;
15593     int whiteToPlay;
15594     char buf[128];
15595     char *p, *q;
15596     int emptycount;
15597     ChessSquare piece;
15598
15599     whiteToPlay = (gameMode == EditPosition) ?
15600       !blackPlaysFirst : (move % 2 == 0);
15601     p = buf;
15602
15603     /* Piece placement data */
15604     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15605         emptycount = 0;
15606         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15607             if (boards[move][i][j] == EmptySquare) {
15608                 emptycount++;
15609             } else { ChessSquare piece = boards[move][i][j];
15610                 if (emptycount > 0) {
15611                     if(emptycount<10) /* [HGM] can be >= 10 */
15612                         *p++ = '0' + emptycount;
15613                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15614                     emptycount = 0;
15615                 }
15616                 if(PieceToChar(piece) == '+') {
15617                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15618                     *p++ = '+';
15619                     piece = (ChessSquare)(DEMOTED piece);
15620                 }
15621                 *p++ = PieceToChar(piece);
15622                 if(p[-1] == '~') {
15623                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15624                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15625                     *p++ = '~';
15626                 }
15627             }
15628         }
15629         if (emptycount > 0) {
15630             if(emptycount<10) /* [HGM] can be >= 10 */
15631                 *p++ = '0' + emptycount;
15632             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15633             emptycount = 0;
15634         }
15635         *p++ = '/';
15636     }
15637     *(p - 1) = ' ';
15638
15639     /* [HGM] print Crazyhouse or Shogi holdings */
15640     if( gameInfo.holdingsWidth ) {
15641         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15642         q = p;
15643         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15644             piece = boards[move][i][BOARD_WIDTH-1];
15645             if( piece != EmptySquare )
15646               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15647                   *p++ = PieceToChar(piece);
15648         }
15649         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15650             piece = boards[move][BOARD_HEIGHT-i-1][0];
15651             if( piece != EmptySquare )
15652               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15653                   *p++ = PieceToChar(piece);
15654         }
15655
15656         if( q == p ) *p++ = '-';
15657         *p++ = ']';
15658         *p++ = ' ';
15659     }
15660
15661     /* Active color */
15662     *p++ = whiteToPlay ? 'w' : 'b';
15663     *p++ = ' ';
15664
15665   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15666     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15667   } else {
15668   if(nrCastlingRights) {
15669      q = p;
15670      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15671        /* [HGM] write directly from rights */
15672            if(boards[move][CASTLING][2] != NoRights &&
15673               boards[move][CASTLING][0] != NoRights   )
15674                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15675            if(boards[move][CASTLING][2] != NoRights &&
15676               boards[move][CASTLING][1] != NoRights   )
15677                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15678            if(boards[move][CASTLING][5] != NoRights &&
15679               boards[move][CASTLING][3] != NoRights   )
15680                 *p++ = boards[move][CASTLING][3] + AAA;
15681            if(boards[move][CASTLING][5] != NoRights &&
15682               boards[move][CASTLING][4] != NoRights   )
15683                 *p++ = boards[move][CASTLING][4] + AAA;
15684      } else {
15685
15686         /* [HGM] write true castling rights */
15687         if( nrCastlingRights == 6 ) {
15688             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15689                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15690             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15691                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15692             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15693                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15694             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15695                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15696         }
15697      }
15698      if (q == p) *p++ = '-'; /* No castling rights */
15699      *p++ = ' ';
15700   }
15701
15702   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15703      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15704     /* En passant target square */
15705     if (move > backwardMostMove) {
15706         fromX = moveList[move - 1][0] - AAA;
15707         fromY = moveList[move - 1][1] - ONE;
15708         toX = moveList[move - 1][2] - AAA;
15709         toY = moveList[move - 1][3] - ONE;
15710         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15711             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15712             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15713             fromX == toX) {
15714             /* 2-square pawn move just happened */
15715             *p++ = toX + AAA;
15716             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15717         } else {
15718             *p++ = '-';
15719         }
15720     } else if(move == backwardMostMove) {
15721         // [HGM] perhaps we should always do it like this, and forget the above?
15722         if((signed char)boards[move][EP_STATUS] >= 0) {
15723             *p++ = boards[move][EP_STATUS] + AAA;
15724             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15725         } else {
15726             *p++ = '-';
15727         }
15728     } else {
15729         *p++ = '-';
15730     }
15731     *p++ = ' ';
15732   }
15733   }
15734
15735     /* [HGM] find reversible plies */
15736     {   int i = 0, j=move;
15737
15738         if (appData.debugMode) { int k;
15739             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15740             for(k=backwardMostMove; k<=forwardMostMove; k++)
15741                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15742
15743         }
15744
15745         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15746         if( j == backwardMostMove ) i += initialRulePlies;
15747         sprintf(p, "%d ", i);
15748         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15749     }
15750     /* Fullmove number */
15751     sprintf(p, "%d", (move / 2) + 1);
15752
15753     return StrSave(buf);
15754 }
15755
15756 Boolean
15757 ParseFEN(board, blackPlaysFirst, fen)
15758     Board board;
15759      int *blackPlaysFirst;
15760      char *fen;
15761 {
15762     int i, j;
15763     char *p, c;
15764     int emptycount;
15765     ChessSquare piece;
15766
15767     p = fen;
15768
15769     /* [HGM] by default clear Crazyhouse holdings, if present */
15770     if(gameInfo.holdingsWidth) {
15771        for(i=0; i<BOARD_HEIGHT; i++) {
15772            board[i][0]             = EmptySquare; /* black holdings */
15773            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15774            board[i][1]             = (ChessSquare) 0; /* black counts */
15775            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15776        }
15777     }
15778
15779     /* Piece placement data */
15780     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15781         j = 0;
15782         for (;;) {
15783             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15784                 if (*p == '/') p++;
15785                 emptycount = gameInfo.boardWidth - j;
15786                 while (emptycount--)
15787                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15788                 break;
15789 #if(BOARD_FILES >= 10)
15790             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15791                 p++; emptycount=10;
15792                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15793                 while (emptycount--)
15794                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15795 #endif
15796             } else if (isdigit(*p)) {
15797                 emptycount = *p++ - '0';
15798                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15799                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15800                 while (emptycount--)
15801                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15802             } else if (*p == '+' || isalpha(*p)) {
15803                 if (j >= gameInfo.boardWidth) return FALSE;
15804                 if(*p=='+') {
15805                     piece = CharToPiece(*++p);
15806                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15807                     piece = (ChessSquare) (PROMOTED piece ); p++;
15808                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15809                 } else piece = CharToPiece(*p++);
15810
15811                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15812                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15813                     piece = (ChessSquare) (PROMOTED piece);
15814                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15815                     p++;
15816                 }
15817                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15818             } else {
15819                 return FALSE;
15820             }
15821         }
15822     }
15823     while (*p == '/' || *p == ' ') p++;
15824
15825     /* [HGM] look for Crazyhouse holdings here */
15826     while(*p==' ') p++;
15827     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15828         if(*p == '[') p++;
15829         if(*p == '-' ) p++; /* empty holdings */ else {
15830             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15831             /* if we would allow FEN reading to set board size, we would   */
15832             /* have to add holdings and shift the board read so far here   */
15833             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15834                 p++;
15835                 if((int) piece >= (int) BlackPawn ) {
15836                     i = (int)piece - (int)BlackPawn;
15837                     i = PieceToNumber((ChessSquare)i);
15838                     if( i >= gameInfo.holdingsSize ) return FALSE;
15839                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15840                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15841                 } else {
15842                     i = (int)piece - (int)WhitePawn;
15843                     i = PieceToNumber((ChessSquare)i);
15844                     if( i >= gameInfo.holdingsSize ) return FALSE;
15845                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15846                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15847                 }
15848             }
15849         }
15850         if(*p == ']') p++;
15851     }
15852
15853     while(*p == ' ') p++;
15854
15855     /* Active color */
15856     c = *p++;
15857     if(appData.colorNickNames) {
15858       if( c == appData.colorNickNames[0] ) c = 'w'; else
15859       if( c == appData.colorNickNames[1] ) c = 'b';
15860     }
15861     switch (c) {
15862       case 'w':
15863         *blackPlaysFirst = FALSE;
15864         break;
15865       case 'b':
15866         *blackPlaysFirst = TRUE;
15867         break;
15868       default:
15869         return FALSE;
15870     }
15871
15872     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15873     /* return the extra info in global variiables             */
15874
15875     /* set defaults in case FEN is incomplete */
15876     board[EP_STATUS] = EP_UNKNOWN;
15877     for(i=0; i<nrCastlingRights; i++ ) {
15878         board[CASTLING][i] =
15879             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15880     }   /* assume possible unless obviously impossible */
15881     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15882     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15883     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15884                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15885     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15886     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15887     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15888                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15889     FENrulePlies = 0;
15890
15891     while(*p==' ') p++;
15892     if(nrCastlingRights) {
15893       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15894           /* castling indicator present, so default becomes no castlings */
15895           for(i=0; i<nrCastlingRights; i++ ) {
15896                  board[CASTLING][i] = NoRights;
15897           }
15898       }
15899       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15900              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15901              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15902              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15903         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15904
15905         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15906             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15907             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15908         }
15909         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15910             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15911         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15912                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15913         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15914                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15915         switch(c) {
15916           case'K':
15917               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15918               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15919               board[CASTLING][2] = whiteKingFile;
15920               break;
15921           case'Q':
15922               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15923               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15924               board[CASTLING][2] = whiteKingFile;
15925               break;
15926           case'k':
15927               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15928               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15929               board[CASTLING][5] = blackKingFile;
15930               break;
15931           case'q':
15932               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15933               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15934               board[CASTLING][5] = blackKingFile;
15935           case '-':
15936               break;
15937           default: /* FRC castlings */
15938               if(c >= 'a') { /* black rights */
15939                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15940                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15941                   if(i == BOARD_RGHT) break;
15942                   board[CASTLING][5] = i;
15943                   c -= AAA;
15944                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15945                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15946                   if(c > i)
15947                       board[CASTLING][3] = c;
15948                   else
15949                       board[CASTLING][4] = c;
15950               } else { /* white rights */
15951                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15952                     if(board[0][i] == WhiteKing) break;
15953                   if(i == BOARD_RGHT) break;
15954                   board[CASTLING][2] = i;
15955                   c -= AAA - 'a' + 'A';
15956                   if(board[0][c] >= WhiteKing) break;
15957                   if(c > i)
15958                       board[CASTLING][0] = c;
15959                   else
15960                       board[CASTLING][1] = c;
15961               }
15962         }
15963       }
15964       for(i=0; i<nrCastlingRights; i++)
15965         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15966     if (appData.debugMode) {
15967         fprintf(debugFP, "FEN castling rights:");
15968         for(i=0; i<nrCastlingRights; i++)
15969         fprintf(debugFP, " %d", board[CASTLING][i]);
15970         fprintf(debugFP, "\n");
15971     }
15972
15973       while(*p==' ') p++;
15974     }
15975
15976     /* read e.p. field in games that know e.p. capture */
15977     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15978        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15979       if(*p=='-') {
15980         p++; board[EP_STATUS] = EP_NONE;
15981       } else {
15982          char c = *p++ - AAA;
15983
15984          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15985          if(*p >= '0' && *p <='9') p++;
15986          board[EP_STATUS] = c;
15987       }
15988     }
15989
15990
15991     if(sscanf(p, "%d", &i) == 1) {
15992         FENrulePlies = i; /* 50-move ply counter */
15993         /* (The move number is still ignored)    */
15994     }
15995
15996     return TRUE;
15997 }
15998
15999 void
16000 EditPositionPasteFEN(char *fen)
16001 {
16002   if (fen != NULL) {
16003     Board initial_position;
16004
16005     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16006       DisplayError(_("Bad FEN position in clipboard"), 0);
16007       return ;
16008     } else {
16009       int savedBlackPlaysFirst = blackPlaysFirst;
16010       EditPositionEvent();
16011       blackPlaysFirst = savedBlackPlaysFirst;
16012       CopyBoard(boards[0], initial_position);
16013       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16014       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16015       DisplayBothClocks();
16016       DrawPosition(FALSE, boards[currentMove]);
16017     }
16018   }
16019 }
16020
16021 static char cseq[12] = "\\   ";
16022
16023 Boolean set_cont_sequence(char *new_seq)
16024 {
16025     int len;
16026     Boolean ret;
16027
16028     // handle bad attempts to set the sequence
16029         if (!new_seq)
16030                 return 0; // acceptable error - no debug
16031
16032     len = strlen(new_seq);
16033     ret = (len > 0) && (len < sizeof(cseq));
16034     if (ret)
16035       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16036     else if (appData.debugMode)
16037       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16038     return ret;
16039 }
16040
16041 /*
16042     reformat a source message so words don't cross the width boundary.  internal
16043     newlines are not removed.  returns the wrapped size (no null character unless
16044     included in source message).  If dest is NULL, only calculate the size required
16045     for the dest buffer.  lp argument indicats line position upon entry, and it's
16046     passed back upon exit.
16047 */
16048 int wrap(char *dest, char *src, int count, int width, int *lp)
16049 {
16050     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16051
16052     cseq_len = strlen(cseq);
16053     old_line = line = *lp;
16054     ansi = len = clen = 0;
16055
16056     for (i=0; i < count; i++)
16057     {
16058         if (src[i] == '\033')
16059             ansi = 1;
16060
16061         // if we hit the width, back up
16062         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16063         {
16064             // store i & len in case the word is too long
16065             old_i = i, old_len = len;
16066
16067             // find the end of the last word
16068             while (i && src[i] != ' ' && src[i] != '\n')
16069             {
16070                 i--;
16071                 len--;
16072             }
16073
16074             // word too long?  restore i & len before splitting it
16075             if ((old_i-i+clen) >= width)
16076             {
16077                 i = old_i;
16078                 len = old_len;
16079             }
16080
16081             // extra space?
16082             if (i && src[i-1] == ' ')
16083                 len--;
16084
16085             if (src[i] != ' ' && src[i] != '\n')
16086             {
16087                 i--;
16088                 if (len)
16089                     len--;
16090             }
16091
16092             // now append the newline and continuation sequence
16093             if (dest)
16094                 dest[len] = '\n';
16095             len++;
16096             if (dest)
16097                 strncpy(dest+len, cseq, cseq_len);
16098             len += cseq_len;
16099             line = cseq_len;
16100             clen = cseq_len;
16101             continue;
16102         }
16103
16104         if (dest)
16105             dest[len] = src[i];
16106         len++;
16107         if (!ansi)
16108             line++;
16109         if (src[i] == '\n')
16110             line = 0;
16111         if (src[i] == 'm')
16112             ansi = 0;
16113     }
16114     if (dest && appData.debugMode)
16115     {
16116         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16117             count, width, line, len, *lp);
16118         show_bytes(debugFP, src, count);
16119         fprintf(debugFP, "\ndest: ");
16120         show_bytes(debugFP, dest, len);
16121         fprintf(debugFP, "\n");
16122     }
16123     *lp = dest ? line : old_line;
16124
16125     return len;
16126 }
16127
16128 // [HGM] vari: routines for shelving variations
16129
16130 void
16131 PushInner(int firstMove, int lastMove)
16132 {
16133         int i, j, nrMoves = lastMove - firstMove;
16134
16135         // push current tail of game on stack
16136         savedResult[storedGames] = gameInfo.result;
16137         savedDetails[storedGames] = gameInfo.resultDetails;
16138         gameInfo.resultDetails = NULL;
16139         savedFirst[storedGames] = firstMove;
16140         savedLast [storedGames] = lastMove;
16141         savedFramePtr[storedGames] = framePtr;
16142         framePtr -= nrMoves; // reserve space for the boards
16143         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16144             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16145             for(j=0; j<MOVE_LEN; j++)
16146                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16147             for(j=0; j<2*MOVE_LEN; j++)
16148                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16149             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16150             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16151             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16152             pvInfoList[firstMove+i-1].depth = 0;
16153             commentList[framePtr+i] = commentList[firstMove+i];
16154             commentList[firstMove+i] = NULL;
16155         }
16156
16157         storedGames++;
16158         forwardMostMove = firstMove; // truncate game so we can start variation
16159 }
16160
16161 void
16162 PushTail(int firstMove, int lastMove)
16163 {
16164         if(appData.icsActive) { // only in local mode
16165                 forwardMostMove = currentMove; // mimic old ICS behavior
16166                 return;
16167         }
16168         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16169
16170         PushInner(firstMove, lastMove);
16171         if(storedGames == 1) GreyRevert(FALSE);
16172 }
16173
16174 void
16175 PopInner(Boolean annotate)
16176 {
16177         int i, j, nrMoves;
16178         char buf[8000], moveBuf[20];
16179
16180         storedGames--;
16181         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16182         nrMoves = savedLast[storedGames] - currentMove;
16183         if(annotate) {
16184                 int cnt = 10;
16185                 if(!WhiteOnMove(currentMove))
16186                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16187                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16188                 for(i=currentMove; i<forwardMostMove; i++) {
16189                         if(WhiteOnMove(i))
16190                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16191                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16192                         strcat(buf, moveBuf);
16193                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16194                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16195                 }
16196                 strcat(buf, ")");
16197         }
16198         for(i=1; i<=nrMoves; i++) { // copy last variation back
16199             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16200             for(j=0; j<MOVE_LEN; j++)
16201                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16202             for(j=0; j<2*MOVE_LEN; j++)
16203                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16204             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16205             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16206             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16207             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16208             commentList[currentMove+i] = commentList[framePtr+i];
16209             commentList[framePtr+i] = NULL;
16210         }
16211         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16212         framePtr = savedFramePtr[storedGames];
16213         gameInfo.result = savedResult[storedGames];
16214         if(gameInfo.resultDetails != NULL) {
16215             free(gameInfo.resultDetails);
16216       }
16217         gameInfo.resultDetails = savedDetails[storedGames];
16218         forwardMostMove = currentMove + nrMoves;
16219 }
16220
16221 Boolean
16222 PopTail(Boolean annotate)
16223 {
16224         if(appData.icsActive) return FALSE; // only in local mode
16225         if(!storedGames) return FALSE; // sanity
16226         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16227
16228         PopInner(annotate);
16229
16230         if(storedGames == 0) GreyRevert(TRUE);
16231         return TRUE;
16232 }
16233
16234 void
16235 CleanupTail()
16236 {       // remove all shelved variations
16237         int i;
16238         for(i=0; i<storedGames; i++) {
16239             if(savedDetails[i])
16240                 free(savedDetails[i]);
16241             savedDetails[i] = NULL;
16242         }
16243         for(i=framePtr; i<MAX_MOVES; i++) {
16244                 if(commentList[i]) free(commentList[i]);
16245                 commentList[i] = NULL;
16246         }
16247         framePtr = MAX_MOVES-1;
16248         storedGames = 0;
16249 }
16250
16251 void
16252 LoadVariation(int index, char *text)
16253 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16254         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16255         int level = 0, move;
16256
16257         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16258         // first find outermost bracketing variation
16259         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16260             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16261                 if(*p == '{') wait = '}'; else
16262                 if(*p == '[') wait = ']'; else
16263                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16264                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16265             }
16266             if(*p == wait) wait = NULLCHAR; // closing ]} found
16267             p++;
16268         }
16269         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16270         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16271         end[1] = NULLCHAR; // clip off comment beyond variation
16272         ToNrEvent(currentMove-1);
16273         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16274         // kludge: use ParsePV() to append variation to game
16275         move = currentMove;
16276         ParsePV(start, TRUE, TRUE);
16277         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16278         ClearPremoveHighlights();
16279         CommentPopDown();
16280         ToNrEvent(currentMove+1);
16281 }
16282